Compare commits
	
		
			12 Commits
		
	
	
		
			4f46f715aa
			...
			478020f6f2
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					478020f6f2 | ||
| 
						 | 
					f5e5d1f72f | ||
| 
						 | 
					d64457ad35 | ||
| 
						 | 
					c8d71996d9 | ||
| 
						 | 
					fc214ed696 | ||
| 
						 | 
					4ec99934e4 | ||
| 
						 | 
					b0d5429ae6 | ||
| 
						 | 
					d089276b39 | ||
| 
						 | 
					db12fca1c3 | ||
| 
						 | 
					2052492912 | ||
| 
						 | 
					975802dc88 | ||
| 
						 | 
					76642b89d5 | 
@ -4,17 +4,20 @@ import UIKit
 | 
			
		||||
struct RefreshableScrollView<Content: View>: UIViewRepresentable {
 | 
			
		||||
    var content: Content
 | 
			
		||||
    var onRefresh: () -> Void
 | 
			
		||||
    var onScroll: ((CGPoint) -> Void)?
 | 
			
		||||
    var isRefreshing: Binding<Bool>
 | 
			
		||||
 | 
			
		||||
    init(isRefreshing: Binding<Bool>, onRefresh: @escaping () -> Void, @ViewBuilder content: () -> Content) {
 | 
			
		||||
    init(isRefreshing: Binding<Bool>, onRefresh: @escaping () -> Void, onScroll: ((CGPoint) -> Void)? = nil, @ViewBuilder content: () -> Content) {
 | 
			
		||||
        self.content = content()
 | 
			
		||||
        self.onRefresh = onRefresh
 | 
			
		||||
        self.onScroll = onScroll
 | 
			
		||||
        self.isRefreshing = isRefreshing
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    func makeUIView(context: Context) -> UIScrollView {
 | 
			
		||||
        let scrollView = UIScrollView()
 | 
			
		||||
        scrollView.delaysContentTouches = false
 | 
			
		||||
        scrollView.delegate = context.coordinator
 | 
			
		||||
        
 | 
			
		||||
        let refreshControl = UIRefreshControl()
 | 
			
		||||
        refreshControl.addTarget(context.coordinator, action: #selector(Coordinator.handleRefresh), for: .valueChanged)
 | 
			
		||||
@ -41,7 +44,6 @@ struct RefreshableScrollView<Content: View>: UIViewRepresentable {
 | 
			
		||||
        if isRefreshing.wrappedValue {
 | 
			
		||||
            uiView.refreshControl?.beginRefreshing()
 | 
			
		||||
        } else {
 | 
			
		||||
            // Отложенное завершение, чтобы избежать цикла обновлений
 | 
			
		||||
            DispatchQueue.main.async {
 | 
			
		||||
                uiView.refreshControl?.endRefreshing()
 | 
			
		||||
            }
 | 
			
		||||
@ -54,7 +56,7 @@ struct RefreshableScrollView<Content: View>: UIViewRepresentable {
 | 
			
		||||
        Coordinator(self)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    class Coordinator: NSObject {
 | 
			
		||||
    class Coordinator: NSObject, UIScrollViewDelegate {
 | 
			
		||||
        var parent: RefreshableScrollView
 | 
			
		||||
        var hostingController: UIHostingController<Content>?
 | 
			
		||||
 | 
			
		||||
@ -65,5 +67,9 @@ struct RefreshableScrollView<Content: View>: UIViewRepresentable {
 | 
			
		||||
        @objc func handleRefresh() {
 | 
			
		||||
            parent.onRefresh()
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        func scrollViewDidScroll(_ scrollView: UIScrollView) {
 | 
			
		||||
            parent.onScroll?(scrollView.contentOffset)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										104
									
								
								Shared/Components/TopBarView.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								Shared/Components/TopBarView.swift
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,104 @@
 | 
			
		||||
import SwiftUI
 | 
			
		||||
 | 
			
		||||
struct TopBarView: View {
 | 
			
		||||
    var title: String
 | 
			
		||||
    
 | 
			
		||||
    // Состояния для ProfileTab
 | 
			
		||||
    @Binding var selectedAccount: String
 | 
			
		||||
    @Binding var sheetType: ProfileTab.SheetType?
 | 
			
		||||
    var accounts: [String]
 | 
			
		||||
    var viewModel: LoginViewModel
 | 
			
		||||
    
 | 
			
		||||
    // Привязка для управления боковым меню
 | 
			
		||||
    @Binding var isSideMenuPresented: Bool
 | 
			
		||||
    
 | 
			
		||||
    var isHomeTab: Bool {
 | 
			
		||||
        return title == "Home"
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    var isProfileTab: Bool {
 | 
			
		||||
        return title == "Profile"
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    var body: some View {
 | 
			
		||||
        VStack(spacing: 0) {
 | 
			
		||||
            HStack {
 | 
			
		||||
                // Кнопка "Гамбургер" для открытия меню
 | 
			
		||||
                Button(action: {
 | 
			
		||||
                    withAnimation {
 | 
			
		||||
                        isSideMenuPresented.toggle()
 | 
			
		||||
                    }
 | 
			
		||||
                }) {
 | 
			
		||||
                    Image(systemName: "line.horizontal.3")
 | 
			
		||||
                        .imageScale(.large)
 | 
			
		||||
                        .foregroundColor(.primary)
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
//                Spacer()
 | 
			
		||||
                
 | 
			
		||||
                if isHomeTab{
 | 
			
		||||
                    Text("Yobble")
 | 
			
		||||
                        .font(.largeTitle)
 | 
			
		||||
                        .fontWeight(.bold)
 | 
			
		||||
                    Spacer()
 | 
			
		||||
                } else if isProfileTab {
 | 
			
		||||
                    Spacer()
 | 
			
		||||
                    Button(action: { sheetType = .accountShare }) {
 | 
			
		||||
                        HStack(spacing: 4) {
 | 
			
		||||
                            Text(selectedAccount)
 | 
			
		||||
                                .font(.headline)
 | 
			
		||||
                                .foregroundColor(.primary)
 | 
			
		||||
                            Image(systemName: "chevron.down")
 | 
			
		||||
                                .font(.subheadline)
 | 
			
		||||
                                .foregroundColor(.gray)
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    Spacer()
 | 
			
		||||
                } else {
 | 
			
		||||
                    Text(title)
 | 
			
		||||
                        .font(.largeTitle)
 | 
			
		||||
                        .fontWeight(.bold)
 | 
			
		||||
                    Spacer()
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                if isHomeTab{
 | 
			
		||||
                    HStack(spacing: 20) {
 | 
			
		||||
                        // Кнопка поиска
 | 
			
		||||
                        Button(action: {
 | 
			
		||||
                            // пока ничего не делаем
 | 
			
		||||
                        }) {
 | 
			
		||||
                            Image(systemName: "magnifyingglass")
 | 
			
		||||
                                .imageScale(.large)
 | 
			
		||||
                                .foregroundColor(.primary)
 | 
			
		||||
                        }
 | 
			
		||||
                        // Кнопка уведомлений
 | 
			
		||||
                        Button(action: {
 | 
			
		||||
                            // пока ничего не делаем
 | 
			
		||||
                        }) {
 | 
			
		||||
                            Image(systemName: "bell")
 | 
			
		||||
                                .imageScale(.large)
 | 
			
		||||
                                .foregroundColor(.primary)
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                } else if isProfileTab {
 | 
			
		||||
                    NavigationLink(destination: SettingsView(viewModel: viewModel)) {
 | 
			
		||||
                        Image(systemName: "wrench")
 | 
			
		||||
                            .imageScale(.large)
 | 
			
		||||
                            .foregroundColor(.primary)
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            .padding()
 | 
			
		||||
            .frame(height: 50) // Стандартная высота для нав. бара
 | 
			
		||||
            
 | 
			
		||||
//            Divider()
 | 
			
		||||
        }
 | 
			
		||||
        .background(Color(UIColor.systemBackground))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct TopBarView_Previews: PreviewProvider {
 | 
			
		||||
    static var previews: some View {
 | 
			
		||||
        /*@START_MENU_TOKEN@*/Text("Hello, World!")/*@END_MENU_TOKEN@*/
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										6
									
								
								Shared/Media/Media.xcassets/Contents.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								Shared/Media/Media.xcassets/Contents.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
  "info" : {
 | 
			
		||||
    "author" : "xcode",
 | 
			
		||||
    "version" : 1
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										21
									
								
								Shared/Media/Media.xcassets/placeholderPhoto.imageset/Contents.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								Shared/Media/Media.xcassets/placeholderPhoto.imageset/Contents.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,21 @@
 | 
			
		||||
{
 | 
			
		||||
  "images" : [
 | 
			
		||||
    {
 | 
			
		||||
      "filename" : "placeholderPhoto.jpg",
 | 
			
		||||
      "idiom" : "universal",
 | 
			
		||||
      "scale" : "1x"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "idiom" : "universal",
 | 
			
		||||
      "scale" : "2x"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "idiom" : "universal",
 | 
			
		||||
      "scale" : "3x"
 | 
			
		||||
    }
 | 
			
		||||
  ],
 | 
			
		||||
  "info" : {
 | 
			
		||||
    "author" : "xcode",
 | 
			
		||||
    "version" : 1
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								Shared/Media/Media.xcassets/placeholderPhoto.imageset/placeholderPhoto.jpg
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Shared/Media/Media.xcassets/placeholderPhoto.imageset/placeholderPhoto.jpg
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 3.0 KiB  | 
							
								
								
									
										21
									
								
								Shared/Media/Media.xcassets/placeholderPhotoSquare.imageset/Contents.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								Shared/Media/Media.xcassets/placeholderPhotoSquare.imageset/Contents.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,21 @@
 | 
			
		||||
{
 | 
			
		||||
  "images" : [
 | 
			
		||||
    {
 | 
			
		||||
      "filename" : "placeholderPhotoSquare.png",
 | 
			
		||||
      "idiom" : "universal",
 | 
			
		||||
      "scale" : "1x"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "idiom" : "universal",
 | 
			
		||||
      "scale" : "2x"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "idiom" : "universal",
 | 
			
		||||
      "scale" : "3x"
 | 
			
		||||
    }
 | 
			
		||||
  ],
 | 
			
		||||
  "info" : {
 | 
			
		||||
    "author" : "xcode",
 | 
			
		||||
    "version" : 1
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								Shared/Media/Media.xcassets/placeholderPhotoSquare.imageset/placeholderPhotoSquare.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Shared/Media/Media.xcassets/placeholderPhotoSquare.imageset/placeholderPhotoSquare.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 173 KiB  | 
@ -0,0 +1,12 @@
 | 
			
		||||
{
 | 
			
		||||
  "data" : [
 | 
			
		||||
    {
 | 
			
		||||
      "filename" : "placeholderVideo.mp4",
 | 
			
		||||
      "idiom" : "universal"
 | 
			
		||||
    }
 | 
			
		||||
  ],
 | 
			
		||||
  "info" : {
 | 
			
		||||
    "author" : "xcode",
 | 
			
		||||
    "version" : 1
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								Shared/Media/placeholderPhoto.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Shared/Media/placeholderPhoto.jpg
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 3.0 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								Shared/Media/placeholderPhotoSquare.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Shared/Media/placeholderPhotoSquare.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 173 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								Shared/Media/placeholderVideo.mp4
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Shared/Media/placeholderVideo.mp4
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							@ -10,7 +10,7 @@ struct Post: Identifiable, Codable {
 | 
			
		||||
    let description: String?               // Описание поста
 | 
			
		||||
    let media: [MediaItem]                 // Массив медиафайлов
 | 
			
		||||
    let mediaOrder: [UUID]?                // Порядок отображения медиа
 | 
			
		||||
    let thumbnailID: UUID?                 // Превью для видео
 | 
			
		||||
    let thumbnailID: UUID                  // Превью
 | 
			
		||||
    let duration: Double?                  // Длительность видео/аудио
 | 
			
		||||
    let createdAt: Date                    // Дата создания
 | 
			
		||||
    let updatedAt: Date                    // Дата обновления
 | 
			
		||||
@ -20,7 +20,7 @@ struct Post: Identifiable, Codable {
 | 
			
		||||
    let commentsCount: Int                 // Кол-во комментариев
 | 
			
		||||
    let isLikedByCurrentUser: Bool         // Лайк текущим юзером
 | 
			
		||||
    let isSavedByCurrentUser: Bool         // Сохранено текущим юзером
 | 
			
		||||
    let authorID: String                   // Автор
 | 
			
		||||
    let authorID: UUID                     // Автор
 | 
			
		||||
    let authorUsername: String             // Имя пользователя автора
 | 
			
		||||
    let hashtags: [String]?                // Хэштеги
 | 
			
		||||
    let location: String?                  // Гео
 | 
			
		||||
@ -30,6 +30,7 @@ struct Post: Identifiable, Codable {
 | 
			
		||||
 | 
			
		||||
enum MediaType: String, Codable {
 | 
			
		||||
    case photo
 | 
			
		||||
    case live
 | 
			
		||||
    case video
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -96,10 +96,13 @@ class PostService {
 | 
			
		||||
 | 
			
		||||
        for i in 0..<32 {
 | 
			
		||||
            let mediaID = UUID()
 | 
			
		||||
            let thumbID = Bool.random() ? UUID() : nil
 | 
			
		||||
//            let thumbID = Bool.random() ? UUID() : nil
 | 
			
		||||
            let thumbID = UUID()
 | 
			
		||||
            let postID = UUID()
 | 
			
		||||
            
 | 
			
		||||
            let authorId = "user_\(Int.random(in: 1...5))"
 | 
			
		||||
            let authorId = UUID()
 | 
			
		||||
            let authorUserName = "username_\(Int.random(in: 1...5))"
 | 
			
		||||
//            let authorId = "user_\(Int.random(in: 1...5))"
 | 
			
		||||
            let post = Post(
 | 
			
		||||
                id: postID,
 | 
			
		||||
                title: sampleTitles[i],
 | 
			
		||||
@ -119,7 +122,7 @@ class PostService {
 | 
			
		||||
                isLikedByCurrentUser: Bool.random(),
 | 
			
		||||
                isSavedByCurrentUser: Bool.random(),
 | 
			
		||||
                authorID: authorId,
 | 
			
		||||
                authorUsername: "username_\(authorId.split(separator: "_").last ?? "")",
 | 
			
		||||
                authorUsername: authorUserName,
 | 
			
		||||
                hashtags: ["#тест", "#видео", "#swiftui", "#ui"].shuffled().prefix(2).map { $0 },
 | 
			
		||||
                location: Bool.random() ? "Москва" : nil,
 | 
			
		||||
                languageCode: Bool.random() ? ["ru", "en"] : ["ru"],
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										32
									
								
								Shared/ViewModels/NewHomeTabViewModel.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								Shared/ViewModels/NewHomeTabViewModel.swift
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,32 @@
 | 
			
		||||
import Foundation
 | 
			
		||||
import Combine
 | 
			
		||||
 | 
			
		||||
class NewHomeTabViewModel: ObservableObject {
 | 
			
		||||
    @Published var posts: [Post] = []
 | 
			
		||||
    @Published var isLoading = true
 | 
			
		||||
    @Published var isRefreshing = false
 | 
			
		||||
    
 | 
			
		||||
    private var hasLoadedData = false
 | 
			
		||||
 | 
			
		||||
    func fetchDataIfNeeded() {
 | 
			
		||||
        guard !hasLoadedData else { return }
 | 
			
		||||
        refreshData()
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    func refreshData() {
 | 
			
		||||
        DispatchQueue.main.async {
 | 
			
		||||
            self.isRefreshing = true
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        PostService.shared.fetchAllPosts { [weak self] fetchedPosts in
 | 
			
		||||
            guard let self = self else { return }
 | 
			
		||||
            
 | 
			
		||||
            DispatchQueue.main.async {
 | 
			
		||||
                self.posts = fetchedPosts
 | 
			
		||||
                self.isLoading = false
 | 
			
		||||
                self.hasLoadedData = true
 | 
			
		||||
                self.isRefreshing = false
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -9,16 +9,12 @@ import SwiftUI
 | 
			
		||||
 | 
			
		||||
struct ChatsTab: View {
 | 
			
		||||
    var body: some View {
 | 
			
		||||
        NavigationView {
 | 
			
		||||
        VStack {
 | 
			
		||||
                Text("Чаты")
 | 
			
		||||
                    .font(.largeTitle)
 | 
			
		||||
                    .bold()
 | 
			
		||||
                    .padding()
 | 
			
		||||
            Text("Здесь будут чаты")
 | 
			
		||||
                .font(.title)
 | 
			
		||||
                .foregroundColor(.gray)
 | 
			
		||||
            
 | 
			
		||||
            Spacer()
 | 
			
		||||
        }
 | 
			
		||||
            .navigationTitle("Чаты")
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -5,15 +5,18 @@ struct HomeTab: View {
 | 
			
		||||
    @State private var isLoading = true
 | 
			
		||||
    @State private var isRefreshing = false
 | 
			
		||||
    
 | 
			
		||||
    var onScroll: ((CGPoint) -> Void)?
 | 
			
		||||
 | 
			
		||||
    var body: some View {
 | 
			
		||||
        NavigationView {
 | 
			
		||||
        VStack {
 | 
			
		||||
            if isLoading {
 | 
			
		||||
                ProgressView("Загрузка ленты...")
 | 
			
		||||
            } else {
 | 
			
		||||
                    RefreshableScrollView(isRefreshing: $isRefreshing, onRefresh: {
 | 
			
		||||
                        fetchData()
 | 
			
		||||
                    }) {
 | 
			
		||||
                RefreshableScrollView(
 | 
			
		||||
                    isRefreshing: $isRefreshing,
 | 
			
		||||
                    onRefresh: { fetchData() },
 | 
			
		||||
                    onScroll: onScroll
 | 
			
		||||
                ) {
 | 
			
		||||
                    LazyVStack(spacing: 24) {
 | 
			
		||||
                        ForEach(posts) { post in
 | 
			
		||||
                            PostDetailView(post: post)
 | 
			
		||||
@ -22,18 +25,12 @@ struct HomeTab: View {
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
            .navigationBarTitleDisplayMode(.inline)
 | 
			
		||||
            .toolbar {
 | 
			
		||||
                ToolbarItem(placement: .principal) {}
 | 
			
		||||
            }
 | 
			
		||||
        .onAppear {
 | 
			
		||||
            if posts.isEmpty {
 | 
			
		||||
                fetchData(isInitialLoad: true)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
        .navigationViewStyle(StackNavigationViewStyle())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private func fetchData(isInitialLoad: Bool = false) {
 | 
			
		||||
        if isInitialLoad {
 | 
			
		||||
 | 
			
		||||
@ -1,40 +1,90 @@
 | 
			
		||||
//
 | 
			
		||||
//  MainView.swift
 | 
			
		||||
//  VolnahubApp
 | 
			
		||||
//
 | 
			
		||||
//  Created by cheykrym on 09/06/2025.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
import SwiftUI
 | 
			
		||||
 | 
			
		||||
struct MainView: View {
 | 
			
		||||
    @ObservedObject var viewModel: LoginViewModel
 | 
			
		||||
    @State private var selectedTab: Int = 0
 | 
			
		||||
    @StateObject private var newHomeTabViewModel = NewHomeTabViewModel()
 | 
			
		||||
    
 | 
			
		||||
    // Состояния для TopBarView
 | 
			
		||||
    @State private var selectedAccount = "@user1"
 | 
			
		||||
    @State private var accounts = ["@user1", "@user2", "@user3"]
 | 
			
		||||
    @State private var sheetType: ProfileTab.SheetType? = nil
 | 
			
		||||
    
 | 
			
		||||
    // Состояние для бокового меню
 | 
			
		||||
    @State private var isSideMenuPresented = false
 | 
			
		||||
    
 | 
			
		||||
    private var tabTitle: String {
 | 
			
		||||
        switch selectedTab {
 | 
			
		||||
        case 0: return "Home"
 | 
			
		||||
        case 1: return "Search"
 | 
			
		||||
        case 2: return "Chats"
 | 
			
		||||
        case 3: return "Profile"
 | 
			
		||||
        default: return "Home"
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    var body: some View {
 | 
			
		||||
        VStack(spacing: 0) {
 | 
			
		||||
        NavigationView {
 | 
			
		||||
            ZStack {
 | 
			
		||||
                switch selectedTab {
 | 
			
		||||
                case 0:
 | 
			
		||||
                    HomeTab()
 | 
			
		||||
                case 1:
 | 
			
		||||
                // Основной контент
 | 
			
		||||
                VStack(spacing: 0) {
 | 
			
		||||
                    TopBarView(
 | 
			
		||||
                        title: tabTitle,
 | 
			
		||||
                        selectedAccount: $selectedAccount,
 | 
			
		||||
                        sheetType: $sheetType,
 | 
			
		||||
                        accounts: accounts,
 | 
			
		||||
                        viewModel: viewModel,
 | 
			
		||||
                        isSideMenuPresented: $isSideMenuPresented
 | 
			
		||||
                    )
 | 
			
		||||
                    
 | 
			
		||||
                    ZStack {
 | 
			
		||||
                        NewHomeTab(viewModel: newHomeTabViewModel)
 | 
			
		||||
                            .opacity(selectedTab == 0 ? 1 : 0)
 | 
			
		||||
                        
 | 
			
		||||
                        SearchTab()
 | 
			
		||||
                case 2:
 | 
			
		||||
                            .opacity(selectedTab == 1 ? 1 : 0)
 | 
			
		||||
                        
 | 
			
		||||
                        ChatsTab()
 | 
			
		||||
                case 3:
 | 
			
		||||
                    ProfileTab(viewModel: viewModel)
 | 
			
		||||
                default:
 | 
			
		||||
                    HomeTab()
 | 
			
		||||
                }
 | 
			
		||||
                            .opacity(selectedTab == 2 ? 1 : 0)
 | 
			
		||||
                        
 | 
			
		||||
                        ProfileTab(viewModel: viewModel, sheetType: $sheetType, selectedAccount: $selectedAccount, accounts: $accounts, onScroll: { _ in })
 | 
			
		||||
                            .opacity(selectedTab == 3 ? 1 : 0)
 | 
			
		||||
                    }
 | 
			
		||||
                    .frame(maxWidth: .infinity, maxHeight: .infinity)
 | 
			
		||||
                    
 | 
			
		||||
                    CustomTabBar(selectedTab: $selectedTab) {
 | 
			
		||||
                // Действие для кнопки "Создать"
 | 
			
		||||
                        print("Create button tapped")
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                .ignoresSafeArea(edges: .bottom)
 | 
			
		||||
                .navigationBarHidden(true)
 | 
			
		||||
                .sheet(item: $sheetType) { type in
 | 
			
		||||
                    // ... sheet presentation logic
 | 
			
		||||
                }
 | 
			
		||||
                // Затемнение фона при открытом меню
 | 
			
		||||
                .background(isSideMenuPresented ? Color.black.opacity(0.4) : Color.clear)
 | 
			
		||||
                .onTapGesture {
 | 
			
		||||
                    if isSideMenuPresented {
 | 
			
		||||
                        withAnimation {
 | 
			
		||||
                            isSideMenuPresented = false
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Боковое меню
 | 
			
		||||
                if isSideMenuPresented {
 | 
			
		||||
                    HStack {
 | 
			
		||||
                        SideMenuView(isPresented: $isSideMenuPresented)
 | 
			
		||||
                            .frame(width: UIScreen.main.bounds.width * 0.8)
 | 
			
		||||
                        Spacer()
 | 
			
		||||
                    }
 | 
			
		||||
                    .ignoresSafeArea(edges: .vertical) // Игнорируем safe area
 | 
			
		||||
                    .transition(.move(edge: .leading))
 | 
			
		||||
                    .zIndex(1) // Убедимся, что меню поверх всего
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        .navigationViewStyle(StackNavigationViewStyle())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										114
									
								
								Shared/Views/Tab/NewHomeTab.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								Shared/Views/Tab/NewHomeTab.swift
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,114 @@
 | 
			
		||||
import SwiftUI
 | 
			
		||||
 | 
			
		||||
struct NewHomeTab: View {
 | 
			
		||||
    @ObservedObject var viewModel: NewHomeTabViewModel
 | 
			
		||||
    
 | 
			
		||||
    private var column1Posts: [Post] {
 | 
			
		||||
        viewModel.posts.enumerated().filter { $0.offset % 2 == 0 }.map { $0.element }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private var column2Posts: [Post] {
 | 
			
		||||
        viewModel.posts.enumerated().filter { $0.offset % 2 != 0 }.map { $0.element }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    var body: some View {
 | 
			
		||||
        VStack {
 | 
			
		||||
            if viewModel.isLoading {
 | 
			
		||||
                ProgressView("Загрузка ленты...")
 | 
			
		||||
            } else {
 | 
			
		||||
                RefreshableScrollView(isRefreshing: $viewModel.isRefreshing, onRefresh: {
 | 
			
		||||
                    viewModel.refreshData()
 | 
			
		||||
                }) {
 | 
			
		||||
                    HStack(alignment: .top, spacing: 6) {
 | 
			
		||||
                        LazyVStack(spacing: 6) {
 | 
			
		||||
                            ForEach(column1Posts) { post in
 | 
			
		||||
                                PostGridItem(post: post)
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        LazyVStack(spacing: 6) {
 | 
			
		||||
                            ForEach(column2Posts) { post in
 | 
			
		||||
                                PostGridItem(post: post)
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    .padding(.horizontal, 4)
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        .onAppear {
 | 
			
		||||
            viewModel.fetchDataIfNeeded()
 | 
			
		||||
        }
 | 
			
		||||
        .background(Color(.secondarySystemBackground)) // Фон для всей вкладки
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct PostGridItem: View {
 | 
			
		||||
    let post: Post
 | 
			
		||||
    
 | 
			
		||||
    private var randomHeight: CGFloat {
 | 
			
		||||
        CGFloat.random(in: 150...300)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    var body: some View {
 | 
			
		||||
        VStack(alignment: .leading, spacing: 0) { // Убираем отступ между картинкой и текстом
 | 
			
		||||
            // 1. Медиа контент
 | 
			
		||||
            if let _ = post.media.first {
 | 
			
		||||
                Image("placeholderPhoto")
 | 
			
		||||
                    .resizable()
 | 
			
		||||
                    .aspectRatio(contentMode: .fill)
 | 
			
		||||
                    .frame(height: randomHeight)
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            // Контейнер для текста, который создает эффект "расширения"
 | 
			
		||||
            VStack(alignment: .leading, spacing: 8) {
 | 
			
		||||
                // 2. Название поста
 | 
			
		||||
                if let title = post.title, !title.isEmpty {
 | 
			
		||||
                    Text(title)
 | 
			
		||||
                        .font(.subheadline)
 | 
			
		||||
                        .lineLimit(2)
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                // 3. Информация об авторе и лайки
 | 
			
		||||
                HStack {
 | 
			
		||||
                    
 | 
			
		||||
                    Button(action: {
 | 
			
		||||
                        // пока ничего не делаем
 | 
			
		||||
                    }) {
 | 
			
		||||
                        HStack(spacing: 4) {
 | 
			
		||||
                        Image(systemName: "person.circle.fill")
 | 
			
		||||
                            .resizable()
 | 
			
		||||
                            .frame(width: 20, height: 20)
 | 
			
		||||
                            .foregroundColor(.gray)
 | 
			
		||||
                        Text(post.authorUsername)
 | 
			
		||||
                            .font(.footnote)
 | 
			
		||||
                            .lineLimit(1)
 | 
			
		||||
                            .foregroundColor(.primary)
 | 
			
		||||
                    }
 | 
			
		||||
                    }
 | 
			
		||||
                    .contentShape(Rectangle())
 | 
			
		||||
 | 
			
		||||
                    Spacer()
 | 
			
		||||
 | 
			
		||||
                    Button(action: {
 | 
			
		||||
                        // пока ничего не делаем
 | 
			
		||||
                    }) {
 | 
			
		||||
                        HStack(spacing: 4) {
 | 
			
		||||
                        Image(systemName: post.isLikedByCurrentUser ? "heart.fill" : "heart")
 | 
			
		||||
                            .foregroundColor(post.isLikedByCurrentUser ? .red : .primary)
 | 
			
		||||
                        Text("\(post.likes)")
 | 
			
		||||
                            .font(.subheadline)
 | 
			
		||||
                            .foregroundColor(.primary)
 | 
			
		||||
                    }
 | 
			
		||||
                    }
 | 
			
		||||
                    .contentShape(Rectangle())
 | 
			
		||||
                    
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            .padding(8)
 | 
			
		||||
            .background(Color(UIColor.systemBackground)) // Фон только для текстовой части
 | 
			
		||||
        }
 | 
			
		||||
        .cornerRadius(6) // Закругляем всю карточку
 | 
			
		||||
        .clipped() // Обрезаем дочерние вью по закругленной форме родителя
 | 
			
		||||
        .shadow(color: Color.black.opacity(0.1), radius: 5, x: 0, y: 2)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -43,15 +43,6 @@ struct ProfileContentTabbedGrid: View {
 | 
			
		||||
                        .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)
 | 
			
		||||
 | 
			
		||||
@ -4,13 +4,16 @@ struct ProfileTab: View {
 | 
			
		||||
    @ObservedObject var viewModel: LoginViewModel
 | 
			
		||||
    @State private var isContentLoaded = true
 | 
			
		||||
    
 | 
			
		||||
    @State private var accounts = ["@user1", "@user2", "@user3"]
 | 
			
		||||
    @State private var selectedAccount = "@user1"
 | 
			
		||||
    // Привязки к состояниям в MainView
 | 
			
		||||
    @Binding var sheetType: SheetType?
 | 
			
		||||
    @Binding var selectedAccount: String
 | 
			
		||||
    @Binding var accounts: [String]
 | 
			
		||||
    
 | 
			
		||||
    var onScroll: ((CGPoint) -> Void)?
 | 
			
		||||
 | 
			
		||||
    let followers = ["@alice", "@bob", "@charlie"]
 | 
			
		||||
    let following = ["@dev", "@design", "@ios"]
 | 
			
		||||
    
 | 
			
		||||
    @State private var sheetType: SheetType? = nil
 | 
			
		||||
    enum SheetType: Identifiable {
 | 
			
		||||
        case accountShare
 | 
			
		||||
        var id: Int { self.hashValue }
 | 
			
		||||
@ -26,7 +29,7 @@ struct ProfileTab: View {
 | 
			
		||||
    @State private var isShowingFollowing = false
 | 
			
		||||
 | 
			
		||||
    var body: some View {
 | 
			
		||||
        NavigationView {
 | 
			
		||||
        VStack {
 | 
			
		||||
            if !isContentLoaded {
 | 
			
		||||
                SplashScreenView()
 | 
			
		||||
            } else {
 | 
			
		||||
@ -46,12 +49,13 @@ struct ProfileTab: View {
 | 
			
		||||
                        )
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    RefreshableScrollView(isRefreshing: $isRefreshing, onRefresh: {
 | 
			
		||||
                        fetchData()
 | 
			
		||||
                    }) {
 | 
			
		||||
                    RefreshableScrollView(
 | 
			
		||||
                        isRefreshing: $isRefreshing,
 | 
			
		||||
                        onRefresh: { fetchData() },
 | 
			
		||||
                        onScroll: onScroll
 | 
			
		||||
                    ) {
 | 
			
		||||
                        VStack(spacing: 12) {
 | 
			
		||||
                            header
 | 
			
		||||
//                                .frame(minHeight: 300)
 | 
			
		||||
                                .frame(maxWidth: .infinity)
 | 
			
		||||
                                .background(Color(.systemBackground))
 | 
			
		||||
                            
 | 
			
		||||
@ -66,43 +70,8 @@ struct ProfileTab: View {
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                .navigationBarTitleDisplayMode(.inline)
 | 
			
		||||
                .toolbar {
 | 
			
		||||
                    ToolbarItem(placement: .principal) {
 | 
			
		||||
                        Button(action: { sheetType = .accountShare }) {
 | 
			
		||||
                            HStack(spacing: 4) {
 | 
			
		||||
                                Text(selectedAccount)
 | 
			
		||||
                                    .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 allPosts.isEmpty {
 | 
			
		||||
                fetchData(isInitialLoad: true)
 | 
			
		||||
 | 
			
		||||
@ -4,13 +4,17 @@ struct SearchTab: View {
 | 
			
		||||
    @StateObject private var viewModel = SearchViewModel()
 | 
			
		||||
    
 | 
			
		||||
    var body: some View {
 | 
			
		||||
        NavigationView {
 | 
			
		||||
        VStack {
 | 
			
		||||
            SearchBar(text: $viewModel.searchText)
 | 
			
		||||
                .padding(.top, 8)
 | 
			
		||||
            
 | 
			
		||||
            List(viewModel.users) { user in
 | 
			
		||||
                    NavigationLink(destination: ProfileTab(viewModel: LoginViewModel())) { // Placeholder destination
 | 
			
		||||
                NavigationLink(destination: ProfileTab(
 | 
			
		||||
                    viewModel: LoginViewModel(),
 | 
			
		||||
                    sheetType: .constant(nil),
 | 
			
		||||
                    selectedAccount: .constant("@\(user.username)"),
 | 
			
		||||
                    accounts: .constant(["@\(user.username)"]))
 | 
			
		||||
                ) {
 | 
			
		||||
                    HStack {
 | 
			
		||||
                        Image(systemName: "person.crop.circle")
 | 
			
		||||
                            .resizable()
 | 
			
		||||
@ -22,8 +26,6 @@ struct SearchTab: View {
 | 
			
		||||
            }
 | 
			
		||||
            .listStyle(PlainListStyle())
 | 
			
		||||
        }
 | 
			
		||||
            .navigationTitle("Поиск")
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										116
									
								
								Shared/Views/Tab/SideMenuView.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										116
									
								
								Shared/Views/Tab/SideMenuView.swift
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,116 @@
 | 
			
		||||
import SwiftUI
 | 
			
		||||
 | 
			
		||||
struct SideMenuView: View {
 | 
			
		||||
    @Binding var isPresented: Bool
 | 
			
		||||
 | 
			
		||||
    var body: some View {
 | 
			
		||||
        VStack(alignment: .leading, spacing: 0) {
 | 
			
		||||
            // Header
 | 
			
		||||
            VStack(alignment: .leading) {
 | 
			
		||||
                Image(systemName: "person.circle.fill")
 | 
			
		||||
                    .resizable()
 | 
			
		||||
                    .frame(width: 60, height: 60)
 | 
			
		||||
                    .foregroundColor(.gray)
 | 
			
		||||
                Text("Your Name")
 | 
			
		||||
                    .font(.title2).bold()
 | 
			
		||||
                Text("@yourusername")
 | 
			
		||||
                    .font(.subheadline)
 | 
			
		||||
                    .foregroundColor(.secondary)
 | 
			
		||||
            }
 | 
			
		||||
            .padding(20)
 | 
			
		||||
 | 
			
		||||
            // Menu Items
 | 
			
		||||
            ScrollView {
 | 
			
		||||
                VStack(alignment: .leading, spacing: 20) {
 | 
			
		||||
                    // Section 1
 | 
			
		||||
                    VStack(alignment: .leading, spacing: 15) {
 | 
			
		||||
                        SideMenuButton(icon: "person.2.fill", title: "People You May Like", action: {})
 | 
			
		||||
                        SideMenuButton(icon: "star.fill", title: "Fun Fest", action: {})
 | 
			
		||||
                        SideMenuButton(icon: "lightbulb.fill", title: "Creator Center", action: {})
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    Divider()
 | 
			
		||||
 | 
			
		||||
                    // Section 2
 | 
			
		||||
                    VStack(alignment: .leading, spacing: 15) {
 | 
			
		||||
                        Text("CATEGORY").font(.caption).foregroundColor(.secondary)
 | 
			
		||||
                        SideMenuButton(icon: "doc.text", title: "Drafts", action: {})
 | 
			
		||||
                        SideMenuButton(icon: "bubble.left", title: "My Comments", action: {})
 | 
			
		||||
                        SideMenuButton(icon: "clock", title: "History", action: {})
 | 
			
		||||
                        SideMenuButton(icon: "arrow.down.circle", title: "My Downloads", action: {})
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    Divider()
 | 
			
		||||
 | 
			
		||||
                    // Section 3
 | 
			
		||||
                    VStack(alignment: .leading, spacing: 15) {
 | 
			
		||||
                        Text("SERVICES").font(.caption).foregroundColor(.secondary)
 | 
			
		||||
                        SideMenuButton(icon: "shippingbox", title: "Orders", action: {})
 | 
			
		||||
                        SideMenuButton(icon: "cart", title: "Cart", action: {})
 | 
			
		||||
                        SideMenuButton(icon: "wallet.pass", title: "Wallet", action: {})
 | 
			
		||||
                    }
 | 
			
		||||
                    
 | 
			
		||||
                    Divider()
 | 
			
		||||
                    
 | 
			
		||||
                    // Section 4
 | 
			
		||||
                    VStack(alignment: .leading, spacing: 15) {
 | 
			
		||||
                        SideMenuButton(icon: "square.grid.2x2", title: "Applets", action: {})
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                .padding()
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            Spacer()
 | 
			
		||||
 | 
			
		||||
            // Footer
 | 
			
		||||
            HStack(spacing: 20) {
 | 
			
		||||
                Spacer()
 | 
			
		||||
                SideMenuFooterButton(icon: "qrcode.viewfinder", title: "Scan", action: {})
 | 
			
		||||
                SideMenuFooterButton(icon: "questionmark.circle", title: "Help Center", action: {})
 | 
			
		||||
                SideMenuFooterButton(icon: "gear", title: "Settings", action: {})
 | 
			
		||||
                Spacer()
 | 
			
		||||
            }
 | 
			
		||||
            .padding()
 | 
			
		||||
        }
 | 
			
		||||
        .background(Color(UIColor.systemBackground))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Helper Views for buttons
 | 
			
		||||
struct SideMenuButton: View {
 | 
			
		||||
    let icon: String
 | 
			
		||||
    let title: String
 | 
			
		||||
    let action: () -> Void
 | 
			
		||||
 | 
			
		||||
    var body: some View {
 | 
			
		||||
        Button(action: action) {
 | 
			
		||||
            HStack(spacing: 15) {
 | 
			
		||||
                Image(systemName: icon)
 | 
			
		||||
                    .font(.title3)
 | 
			
		||||
                    .frame(width: 30)
 | 
			
		||||
                Text(title)
 | 
			
		||||
                    .font(.headline)
 | 
			
		||||
            }
 | 
			
		||||
            .foregroundColor(.primary)
 | 
			
		||||
            .padding(.vertical, 8)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct SideMenuFooterButton: View {
 | 
			
		||||
    let icon: String
 | 
			
		||||
    let title: String
 | 
			
		||||
    let action: () -> Void
 | 
			
		||||
 | 
			
		||||
    var body: some View {
 | 
			
		||||
        Button(action: action) {
 | 
			
		||||
            VStack {
 | 
			
		||||
                Image(systemName: icon)
 | 
			
		||||
                    .font(.title2)
 | 
			
		||||
                Text(title)
 | 
			
		||||
                    .font(.caption)
 | 
			
		||||
            }
 | 
			
		||||
            .foregroundColor(.primary)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -29,9 +29,18 @@
 | 
			
		||||
		1A7940E72DF7B5E5002569DA /* SplashScreenView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A7940E62DF7B5E5002569DA /* SplashScreenView.swift */; };
 | 
			
		||||
		1A7940F02DF7B7A3002569DA /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 1A7940F22DF7B7A3002569DA /* Localizable.strings */; };
 | 
			
		||||
		1A79410C2DF7C81D002569DA /* RegistrationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A79410B2DF7C81D002569DA /* RegistrationView.swift */; };
 | 
			
		||||
		1A9B014B2E4BF3CD00887E0B /* NewHomeTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A9B014A2E4BF3CD00887E0B /* NewHomeTab.swift */; };
 | 
			
		||||
		1A9B01582E4BF50D00887E0B /* placeholderVideo.mp4 in Resources */ = {isa = PBXBuildFile; fileRef = 1A9B01572E4BF50D00887E0B /* placeholderVideo.mp4 */; };
 | 
			
		||||
		1A9B015F2E4BF9C000887E0B /* placeholderPhotoSquare.png in Resources */ = {isa = PBXBuildFile; fileRef = 1A9B015D2E4BF9C000887E0B /* placeholderPhotoSquare.png */; };
 | 
			
		||||
		1A9B01602E4BF9C000887E0B /* placeholderPhoto.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 1A9B015E2E4BF9C000887E0B /* placeholderPhoto.jpg */; };
 | 
			
		||||
		1A9B01662E4BFA3600887E0B /* Media.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1A9B01652E4BFA3600887E0B /* Media.xcassets */; };
 | 
			
		||||
		1A9B016E2E4BFB9000887E0B /* NewHomeTabViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A9B016D2E4BFB9000887E0B /* NewHomeTabViewModel.swift */; };
 | 
			
		||||
		1A9B017C2E4C087F00887E0B /* SideMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A9B017B2E4C087F00887E0B /* SideMenuView.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 */; };
 | 
			
		||||
		1AB7F5162E32EC1C003756F3 /* RefreshableScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AB7F5142E32EC1C003756F3 /* RefreshableScrollView.swift */; };
 | 
			
		||||
		1AB7F5172E32EC1C003756F3 /* TopBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AB7F5152E32EC1C003756F3 /* TopBarView.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 */; };
 | 
			
		||||
@ -40,7 +49,6 @@
 | 
			
		||||
		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 */; };
 | 
			
		||||
		1AD757D12E27640F0069C1FD /* RefreshableScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AD757D02E27640F0069C1FD /* RefreshableScrollView.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 */; };
 | 
			
		||||
@ -65,7 +73,7 @@
 | 
			
		||||
		1A7940A52DF77DF5002569DA /* User.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = "<group>"; };
 | 
			
		||||
		1A7940A92DF77E05002569DA /* LoginViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewModel.swift; sourceTree = "<group>"; };
 | 
			
		||||
		1A7940AF2DF77E26002569DA /* LoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginView.swift; sourceTree = "<group>"; };
 | 
			
		||||
		1A7940B52DF77F21002569DA /* MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainView.swift; sourceTree = "<group>"; };
 | 
			
		||||
		1A7940B52DF77F21002569DA /* MainView.swift */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = MainView.swift; sourceTree = "<group>"; tabWidth = 4; };
 | 
			
		||||
		1A7940C52DF7A98E002569DA /* ContactsTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactsTab.swift; sourceTree = "<group>"; };
 | 
			
		||||
		1A7940C92DF7A99B002569DA /* ChatsTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatsTab.swift; sourceTree = "<group>"; };
 | 
			
		||||
		1A7940CD2DF7A9AA002569DA /* ProfileTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileTab.swift; sourceTree = "<group>"; };
 | 
			
		||||
@ -75,9 +83,18 @@
 | 
			
		||||
		1A7940F12DF7B7A3002569DA /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
 | 
			
		||||
		1A7940F72DF7B7EC002569DA /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = "<group>"; };
 | 
			
		||||
		1A79410B2DF7C81D002569DA /* RegistrationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegistrationView.swift; sourceTree = "<group>"; };
 | 
			
		||||
		1A9B014A2E4BF3CD00887E0B /* NewHomeTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewHomeTab.swift; sourceTree = "<group>"; };
 | 
			
		||||
		1A9B01572E4BF50D00887E0B /* placeholderVideo.mp4 */ = {isa = PBXFileReference; lastKnownFileType = file; name = placeholderVideo.mp4; path = ../../../../Volumes/Untitled/xcode/volnahub/Shared/Media/placeholderVideo.mp4; sourceTree = DEVELOPER_DIR; };
 | 
			
		||||
		1A9B015D2E4BF9C000887E0B /* placeholderPhotoSquare.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = placeholderPhotoSquare.png; path = ../../../../Volumes/Untitled/xcode/volnahub/Shared/Media/placeholderPhotoSquare.png; sourceTree = DEVELOPER_DIR; };
 | 
			
		||||
		1A9B015E2E4BF9C000887E0B /* placeholderPhoto.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; name = placeholderPhoto.jpg; path = ../../../../Volumes/Untitled/xcode/volnahub/Shared/Media/placeholderPhoto.jpg; sourceTree = DEVELOPER_DIR; };
 | 
			
		||||
		1A9B01652E4BFA3600887E0B /* Media.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Media.xcassets; sourceTree = "<group>"; };
 | 
			
		||||
		1A9B016D2E4BFB9000887E0B /* NewHomeTabViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NewHomeTabViewModel.swift; sourceTree = "<group>"; };
 | 
			
		||||
		1A9B017B2E4C087F00887E0B /* SideMenuView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SideMenuView.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>"; };
 | 
			
		||||
		1AB7F5142E32EC1C003756F3 /* RefreshableScrollView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RefreshableScrollView.swift; sourceTree = "<group>"; };
 | 
			
		||||
		1AB7F5152E32EC1C003756F3 /* TopBarView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TopBarView.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>"; };
 | 
			
		||||
@ -86,7 +103,6 @@
 | 
			
		||||
		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>"; };
 | 
			
		||||
		1AD757D02E27640F0069C1FD /* RefreshableScrollView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = RefreshableScrollView.swift; path = Components/RefreshableScrollView.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>"; };
 | 
			
		||||
@ -125,7 +141,8 @@
 | 
			
		||||
		1A7940792DF77BC2002569DA /* Shared */ = {
 | 
			
		||||
			isa = PBXGroup;
 | 
			
		||||
			children = (
 | 
			
		||||
				1AD757D02E27640F0069C1FD /* RefreshableScrollView.swift */,
 | 
			
		||||
				1A9B01502E4BF49200887E0B /* Media */,
 | 
			
		||||
				1AB7F5132E32EBF1003756F3 /* Components */,
 | 
			
		||||
				1A7940FA2DF7B898002569DA /* Resources */,
 | 
			
		||||
				1A7940E52DF7B341002569DA /* Services */,
 | 
			
		||||
				1A7940A02DF77DCD002569DA /* Network */,
 | 
			
		||||
@ -181,6 +198,7 @@
 | 
			
		||||
		1A79409E2DF77DBD002569DA /* ViewModels */ = {
 | 
			
		||||
			isa = PBXGroup;
 | 
			
		||||
			children = (
 | 
			
		||||
				1A9B016D2E4BFB9000887E0B /* NewHomeTabViewModel.swift */,
 | 
			
		||||
				1A2302102E3050C60067BF4F /* SearchViewModel.swift */,
 | 
			
		||||
				1A7940A92DF77E05002569DA /* LoginViewModel.swift */,
 | 
			
		||||
			);
 | 
			
		||||
@ -222,6 +240,26 @@
 | 
			
		||||
			path = Resources;
 | 
			
		||||
			sourceTree = "<group>";
 | 
			
		||||
		};
 | 
			
		||||
		1A9B01502E4BF49200887E0B /* Media */ = {
 | 
			
		||||
			isa = PBXGroup;
 | 
			
		||||
			children = (
 | 
			
		||||
				1A9B01652E4BFA3600887E0B /* Media.xcassets */,
 | 
			
		||||
				1A9B015E2E4BF9C000887E0B /* placeholderPhoto.jpg */,
 | 
			
		||||
				1A9B015D2E4BF9C000887E0B /* placeholderPhotoSquare.png */,
 | 
			
		||||
				1A9B01572E4BF50D00887E0B /* placeholderVideo.mp4 */,
 | 
			
		||||
			);
 | 
			
		||||
			path = Media;
 | 
			
		||||
			sourceTree = "<group>";
 | 
			
		||||
		};
 | 
			
		||||
		1AB7F5132E32EBF1003756F3 /* Components */ = {
 | 
			
		||||
			isa = PBXGroup;
 | 
			
		||||
			children = (
 | 
			
		||||
				1AB7F5142E32EC1C003756F3 /* RefreshableScrollView.swift */,
 | 
			
		||||
				1AB7F5152E32EC1C003756F3 /* TopBarView.swift */,
 | 
			
		||||
			);
 | 
			
		||||
			path = Components;
 | 
			
		||||
			sourceTree = "<group>";
 | 
			
		||||
		};
 | 
			
		||||
		1ACE60FF2E22F54700B37AC5 /* Settings */ = {
 | 
			
		||||
			isa = PBXGroup;
 | 
			
		||||
			children = (
 | 
			
		||||
@ -252,8 +290,10 @@
 | 
			
		||||
		1AEE5EA92E21A4FC00A3DCA3 /* Tab */ = {
 | 
			
		||||
			isa = PBXGroup;
 | 
			
		||||
			children = (
 | 
			
		||||
				1A9B017B2E4C087F00887E0B /* SideMenuView.swift */,
 | 
			
		||||
				1A6FB9542E32D2B200E89EBE /* CustomTabBar.swift */,
 | 
			
		||||
				1A7940B52DF77F21002569DA /* MainView.swift */,
 | 
			
		||||
				1A9B014A2E4BF3CD00887E0B /* NewHomeTab.swift */,
 | 
			
		||||
				1AEE5EAA2E21A83200A3DCA3 /* HomeTab.swift */,
 | 
			
		||||
				1AEE5EB22E21A85800A3DCA3 /* SearchTab.swift */,
 | 
			
		||||
				1A7940C92DF7A99B002569DA /* ChatsTab.swift */,
 | 
			
		||||
@ -359,8 +399,12 @@
 | 
			
		||||
			isa = PBXResourcesBuildPhase;
 | 
			
		||||
			buildActionMask = 2147483647;
 | 
			
		||||
			files = (
 | 
			
		||||
				1A9B01582E4BF50D00887E0B /* placeholderVideo.mp4 in Resources */,
 | 
			
		||||
				1A7940912DF77BC3002569DA /* Assets.xcassets in Resources */,
 | 
			
		||||
				1A7940F02DF7B7A3002569DA /* Localizable.strings in Resources */,
 | 
			
		||||
				1A9B01602E4BF9C000887E0B /* placeholderPhoto.jpg in Resources */,
 | 
			
		||||
				1A9B01662E4BFA3600887E0B /* Media.xcassets in Resources */,
 | 
			
		||||
				1A9B015F2E4BF9C000887E0B /* placeholderPhotoSquare.png in Resources */,
 | 
			
		||||
			);
 | 
			
		||||
			runOnlyForDeploymentPostprocessing = 0;
 | 
			
		||||
		};
 | 
			
		||||
@ -382,22 +426,27 @@
 | 
			
		||||
				1AEE5EAB2E21A83200A3DCA3 /* HomeTab.swift in Sources */,
 | 
			
		||||
				1ACE60F82E22F3DC00B37AC5 /* SettingsView.swift in Sources */,
 | 
			
		||||
				1AD757CD2E27608C0069C1FD /* PostFeedView.swift in Sources */,
 | 
			
		||||
				1A9B017C2E4C087F00887E0B /* SideMenuView.swift in Sources */,
 | 
			
		||||
				1A7940C62DF7A98E002569DA /* ContactsTab.swift in Sources */,
 | 
			
		||||
				1ACE61092E22F57100B37AC5 /* AppPreferencesView.swift in Sources */,
 | 
			
		||||
				1ACE61052E22F56800B37AC5 /* SecuritySettingsView.swift in Sources */,
 | 
			
		||||
				1A9B016E2E4BFB9000887E0B /* NewHomeTabViewModel.swift in Sources */,
 | 
			
		||||
				1A7940E72DF7B5E5002569DA /* SplashScreenView.swift in Sources */,
 | 
			
		||||
				1A7940B02DF77E26002569DA /* LoginView.swift in Sources */,
 | 
			
		||||
				1A79410C2DF7C81D002569DA /* RegistrationView.swift in Sources */,
 | 
			
		||||
				1A0276112DF9247000D8BC53 /* CustomTextField.swift in Sources */,
 | 
			
		||||
				1AB7F5172E32EC1C003756F3 /* TopBarView.swift in Sources */,
 | 
			
		||||
				1A7940CE2DF7A9AA002569DA /* ProfileTab.swift in Sources */,
 | 
			
		||||
				1ACE61212E22FFD000B37AC5 /* ProfileContentTabbedGrid.swift in Sources */,
 | 
			
		||||
				1AB4F8F72E22ECAC002B6E40 /* FollowingView.swift in Sources */,
 | 
			
		||||
				1A7940DE2DF7B0D7002569DA /* config.swift in Sources */,
 | 
			
		||||
				1A9B014B2E4BF3CD00887E0B /* NewHomeTab.swift in Sources */,
 | 
			
		||||
				1A2302112E3050C60067BF4F /* SearchViewModel.swift in Sources */,
 | 
			
		||||
				1AE587252E23337000254F06 /* ProfileContentGrid.swift in Sources */,
 | 
			
		||||
				1A6FB9552E32D2B200E89EBE /* CustomTabBar.swift in Sources */,
 | 
			
		||||
				1A0276032DF909F900D8BC53 /* refreshtokenex.swift in Sources */,
 | 
			
		||||
				1A7940B62DF77F21002569DA /* MainView.swift in Sources */,
 | 
			
		||||
				1AB7F5162E32EC1C003756F3 /* RefreshableScrollView.swift in Sources */,
 | 
			
		||||
				1A7940E22DF7B1C5002569DA /* KeychainService.swift in Sources */,
 | 
			
		||||
				1A7940A62DF77DF5002569DA /* User.swift in Sources */,
 | 
			
		||||
				1A7940A22DF77DE9002569DA /* AuthService.swift in Sources */,
 | 
			
		||||
@ -413,7 +462,6 @@
 | 
			
		||||
				1A7940AA2DF77E05002569DA /* LoginViewModel.swift in Sources */,
 | 
			
		||||
				1AB4F8F32E22EC9F002B6E40 /* FollowersView.swift in Sources */,
 | 
			
		||||
				1A79408D2DF77BC3002569DA /* yobbleApp.swift in Sources */,
 | 
			
		||||
				1AD757D12E27640F0069C1FD /* RefreshableScrollView.swift in Sources */,
 | 
			
		||||
			);
 | 
			
		||||
			runOnlyForDeploymentPostprocessing = 0;
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
@ -17,12 +17,12 @@
 | 
			
		||||
		<key>yobble (iOS).xcscheme_^#shared#^_</key>
 | 
			
		||||
		<dict>
 | 
			
		||||
			<key>orderHint</key>
 | 
			
		||||
			<integer>0</integer>
 | 
			
		||||
			<integer>1</integer>
 | 
			
		||||
		</dict>
 | 
			
		||||
		<key>yobble (macOS).xcscheme_^#shared#^_</key>
 | 
			
		||||
		<dict>
 | 
			
		||||
			<key>orderHint</key>
 | 
			
		||||
			<integer>1</integer>
 | 
			
		||||
			<integer>0</integer>
 | 
			
		||||
		</dict>
 | 
			
		||||
	</dict>
 | 
			
		||||
</dict>
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user