Compare commits

...

7 Commits

Author SHA1 Message Date
cheykrym
3c1f23f447 delete old placeholders 2025-08-14 20:24:19 +03:00
cheykrym
9a9d8038e2 post debug 2025-08-14 20:21:26 +03:00
cheykrym
874e920f6f fix post style 2025-08-14 20:07:12 +03:00
cheykrym
64f59c8e91 add image cache 2025-08-14 19:49:42 +03:00
cheykrym
769cdfdbc7 disable placeholder 2025-08-14 19:43:10 +03:00
cheykrym
a02fa53902 load from api 2025-08-14 19:41:07 +03:00
cheykrym
efc7773c04 homeview patch 2025-08-14 19:33:13 +03:00
12 changed files with 289 additions and 182 deletions

View File

@ -0,0 +1,70 @@
import SwiftUI
import Combine
// 1. Класс для загрузки изображения с поддержкой кеширования
class ImageLoader: ObservableObject {
@Published var image: UIImage?
private var cancellable: AnyCancellable?
private let url: URL
private let cache = ImageCacheManager.shared
init(url: URL) {
self.url = url
}
deinit {
cancellable?.cancel()
}
func load() {
// Проверяем, есть ли изображение в кеше
if let cachedImage = cache.get(forKey: url.absoluteString) {
self.image = cachedImage
return
}
// Если в кеше нет, загружаем из сети
cancellable = URLSession.shared.dataTaskPublisher(for: url)
.map { UIImage(data: $0.data) }
.replaceError(with: nil)
.handleEvents(receiveOutput: { [weak self] image in
// Сохраняем загруженное изображение в кеш
if let image = image, let key = self?.url.absoluteString {
self?.cache.set(image, forKey: key)
}
})
.receive(on: DispatchQueue.main)
.assign(to: \.image, on: self)
}
func cancel() {
cancellable?.cancel()
}
}
// 2. View для отображения удаленного изображения
struct RemoteImageView: View {
@StateObject private var loader: ImageLoader
init(url: URL) {
_loader = StateObject(wrappedValue: ImageLoader(url: url))
}
var body: some View {
content
.onAppear(perform: loader.load)
.onDisappear(perform: loader.cancel)
}
private var content: some View {
Group {
if let image = loader.image {
Image(uiImage: image)
.resizable()
} else {
ProgressView() // Показываем индикатор загрузки
}
}
}
}

View File

@ -1,12 +0,0 @@
{
"data" : [
{
"filename" : "placeholderVideo.mp4",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

View File

@ -37,6 +37,8 @@ enum MediaType: String, Codable {
struct MediaItem: Identifiable, Codable { struct MediaItem: Identifiable, Codable {
let id: UUID let id: UUID
let type: MediaType let type: MediaType
let width: Int?
let height: Int?
} }
enum AccessLevel: String, Codable { enum AccessLevel: String, Codable {

View File

@ -13,102 +13,52 @@ class PostService {
guard posts.isEmpty else { return } guard posts.isEmpty else { return }
let sampleTitles = [ let sampleTitles = [
"Обзор TikTok UI", "Обзор TikTok UI", "Мой первый ролик", "Котик в кадре", "SwiftUI мастер-класс",
"Мой первый ролик", "Анимации и переходы", "Съёмка с дрона", "Урок по дизайну", "Как сделать свайпы",
"Котик в кадре", "Лучший UI 2025", "Мой первый ролик", "Котик в кадре", "SwiftUI мастер-класс",
"SwiftUI мастер-класс", "Анимации и переходы", "Съёмка с дрона", "Урок по дизайну", "Как сделать свайпы",
"Анимации и переходы", "Лучший UI 2025", "Мой первый ролик", "Котик в кадре", "SwiftUI мастер-класс",
"Съёмка с дрона", "Анимации и переходы", "Съёмка с дрона", "Урок по дизайну", "Как сделать свайпы",
"Урок по дизайну", "Лучший UI 2025", "Мой первый ролик", "Котик в кадре", "SwiftUI мастер-класс",
"Как сделать свайпы", "Анимации и переходы", "Съёмка с дрона", "Урок по дизайну", "Как сделать свайпы",
"Лучший UI 2025", "Лучший UI 2025", "Мой первый ролик", "Котик в кадре", "SwiftUI мастер-класс",
"Мой первый ролик", "Анимации и переходы", "Съёмка с дрона", "Урок по дизайну", "Как сделать свайпы",
"Котик в кадре", "Лучший UI 2025", "Завершаем проект"
"SwiftUI мастер-класс",
"Анимации и переходы",
"Съёмка с дрона",
"Урок по дизайну",
"Как сделать свайпы",
"Лучший UI 2025",
"Мой первый ролик",
"Котик в кадре",
"SwiftUI мастер-класс",
"Анимации и переходы",
"Съёмка с дрона",
"Урок по дизайну",
"Как сделать свайпы",
"Лучший UI 2025",
"Мой первый ролик",
"Котик в кадре",
"SwiftUI мастер-класс",
"Анимации и переходы",
"Съёмка с дрона",
"Урок по дизайну",
"Как сделать свайпы",
"Лучший UI 2025",
"Мой первый ролик",
"Котик в кадре",
"SwiftUI мастер-класс",
"Анимации и переходы",
"Съёмка с дрона",
"Урок по дизайну",
"Как сделать свайпы",
"Лучший UI 2025",
"Завершаем проект"
] ]
let sampleDescriptions = [ let sampleDescriptions = [
"Первый тестовый пост с видео", "Первый тестовый пост с видео", "Фейковый контент для ленты", "Видео с котиком 🐱",
"Фейковый контент для ленты", "Интерфейс в стиле TikTok", "Просто тестирую отображение", "Код и UI — любовь",
"Видео с котиком 🐱", "Видео в реальном времени", "Интересный UX пример", "Анимации, переходы, свайпы",
"Интерфейс в стиле TikTok", "Фейковый контент для ленты", "Видео с котиком 🐱", "Интерфейс в стиле TikTok",
"Просто тестирую отображение", "Просто тестирую отображение", "Код и UI — любовь", "Видео в реальном времени",
"Код и UI — любовь", "Интересный UX пример", "Анимации, переходы, свайпы", "Фейковый контент для ленты",
"Видео в реальном времени", "Видео с котиком 🐱", "Интерфейс в стиле TikTok", "Просто тестирую отображение",
"Интересный UX пример", "Код и UI — любовь", "Видео в реальном времени", "Интересный UX пример",
"Анимации, переходы, свайпы", "Анимации, переходы, свайпы", "Фейковый контент для ленты", "Видео с котиком 🐱",
"Фейковый контент для ленты", "Интерфейс в стиле TikTok", "Просто тестирую отображение", "Код и UI — любовь",
"Видео с котиком 🐱", "Видео в реальном времени", "Интересный UX пример", "Анимации, переходы, свайпы",
"Интерфейс в стиле TikTok",
"Просто тестирую отображение",
"Код и UI — любовь",
"Видео в реальном времени",
"Интересный UX пример",
"Анимации, переходы, свайпы",
"Фейковый контент для ленты",
"Видео с котиком 🐱",
"Интерфейс в стиле TikTok",
"Просто тестирую отображение",
"Код и UI — любовь",
"Видео в реальном времени",
"Интересный UX пример",
"Анимации, переходы, свайпы",
"Фейковый контент для ленты",
"Видео с котиком 🐱",
"Интерфейс в стиле TikTok",
"Просто тестирую отображение",
"Код и UI — любовь",
"Видео в реальном времени",
"Интересный UX пример",
"Анимации, переходы, свайпы",
"Вот это да, пост работает!" "Вот это да, пост работает!"
] ]
for i in 0..<32 { for i in 0..<32 {
let mediaID = UUID() let mediaID = UUID()
// let thumbID = Bool.random() ? UUID() : nil
let thumbID = UUID() let thumbID = UUID()
let postID = UUID() let postID = UUID()
let authorId = UUID() let authorId = UUID()
let authorUserName = "username_\(Int.random(in: 1...5))" let authorUserName = "username_\(Int.random(in: 1...5))"
// let authorId = "user_\(Int.random(in: 1...5))"
// Генерируем случайные размеры для изображения
let width = 1080 // Стандартная ширина
let randomRatio = Double.random(in: 1.0...1.77) // от 1:1 до ~9:16
let height = Int(Double(width) * randomRatio)
let post = Post( let post = Post(
id: postID, id: postID,
title: sampleTitles[i], title: sampleTitles[i],
description: sampleDescriptions[i], description: sampleDescriptions[i],
media: [ media: [
MediaItem(id: mediaID, type: .video) MediaItem(id: mediaID, type: .video, width: width, height: height)
], ],
mediaOrder: [mediaID], mediaOrder: [mediaID],
thumbnailID: thumbID, thumbnailID: thumbID,

View File

@ -0,0 +1,22 @@
import UIKit
// Менеджер для кеширования изображений в памяти
class ImageCacheManager {
static let shared = ImageCacheManager()
// NSCache будет хранить UIImage по ключу NSString (URL)
// Он автоматически удаляет объекты при нехватке памяти
private let cache = NSCache<NSString, UIImage>()
private init() {}
// Добавить изображение в кеш
func set(_ image: UIImage, forKey key: String) {
cache.setObject(image, forKey: key as NSString)
}
// Получить изображение из кеша
func get(forKey key: String) -> UIImage? {
return cache.object(forKey: key as NSString)
}
}

View File

@ -1,32 +1,86 @@
import Foundation import Foundation
import Combine import Combine
import UIKit // Для доступа к UIScreen
class NewHomeTabViewModel: ObservableObject { class NewHomeTabViewModel: ObservableObject {
@Published var posts: [Post] = [] @Published var column1Posts: [Post] = []
@Published var isLoading = true @Published var column2Posts: [Post] = []
@Published var isRefreshing = false @Published var isLoading: Bool = false
@Published var isRefreshing: Bool = false
private var hasLoadedData = false private var allPosts: [Post] = []
private let postService = PostService.shared
// Рассчитываем ширину колонки один раз
private let columnWidth = (UIScreen.main.bounds.width - 14) / 2
func fetchDataIfNeeded() { func fetchDataIfNeeded() {
guard !hasLoadedData else { return } guard allPosts.isEmpty else { return }
refreshData() fetchData()
} }
func refreshData() { func refreshData() {
DispatchQueue.main.async { isRefreshing = true
self.isRefreshing = true fetchData()
} }
PostService.shared.fetchAllPosts { [weak self] fetchedPosts in private func fetchData() {
if !isRefreshing {
isLoading = true
}
postService.fetchAllPosts { [weak self] posts in
guard let self = self else { return } guard let self = self else { return }
DispatchQueue.main.async { DispatchQueue.main.async {
self.posts = fetchedPosts self.allPosts = posts
self.distributePosts()
self.isLoading = false self.isLoading = false
self.hasLoadedData = true
self.isRefreshing = false self.isRefreshing = false
} }
} }
} }
private func distributePosts() {
var tempColumn1Posts: [Post] = []
var tempColumn2Posts: [Post] = []
var column1Height: CGFloat = 0
var column2Height: CGFloat = 0
for post in allPosts {
let postHeight = calculatePostHeight(for: post)
// Добавляем пост в более короткую колонку
if column1Height <= column2Height {
tempColumn1Posts.append(post)
column1Height += postHeight
} else {
tempColumn2Posts.append(post)
column2Height += postHeight
}
}
self.column1Posts = tempColumn1Posts
self.column2Posts = tempColumn2Posts
}
private func calculatePostHeight(for post: Post) -> CGFloat {
guard let media = post.media.first,
let width = media.width,
let height = media.height,
width > 0 else {
// Возвращаем стандартную высоту для постов без медиа или с неверными данными
return columnWidth
}
// Рассчитываем высоту изображения на основе его пропорций
let aspectRatio = CGFloat(height) / CGFloat(width)
let imageHeight = columnWidth * aspectRatio
// Здесь можно добавить примерную высоту для текста, если нужно
// Например, + 50-70 поинтов для заголовка, автора и кнопок
// Для простоты пока будем ориентироваться только на высоту картинки
return imageHeight
}
} }

View File

@ -3,13 +3,8 @@ import SwiftUI
struct NewHomeTab: View { struct NewHomeTab: View {
@ObservedObject var viewModel: NewHomeTabViewModel @ObservedObject var viewModel: NewHomeTabViewModel
private var column1Posts: [Post] { // Ширина колонки теперь вычисляется в ViewModel, но нам она нужна и здесь для PostGridItem
viewModel.posts.enumerated().filter { $0.offset % 2 == 0 }.map { $0.element } private let columnWidth = (UIScreen.main.bounds.width - 14) / 2
}
private var column2Posts: [Post] {
viewModel.posts.enumerated().filter { $0.offset % 2 != 0 }.map { $0.element }
}
var body: some View { var body: some View {
VStack { VStack {
@ -21,13 +16,13 @@ struct NewHomeTab: View {
}) { }) {
HStack(alignment: .top, spacing: 6) { HStack(alignment: .top, spacing: 6) {
LazyVStack(spacing: 6) { LazyVStack(spacing: 6) {
ForEach(column1Posts) { post in ForEach(viewModel.column1Posts) { post in
PostGridItem(post: post) PostGridItem(post: post, width: columnWidth)
} }
} }
LazyVStack(spacing: 6) { LazyVStack(spacing: 6) {
ForEach(column2Posts) { post in ForEach(viewModel.column2Posts) { post in
PostGridItem(post: post) PostGridItem(post: post, width: columnWidth)
} }
} }
} }
@ -38,25 +33,51 @@ struct NewHomeTab: View {
.onAppear { .onAppear {
viewModel.fetchDataIfNeeded() viewModel.fetchDataIfNeeded()
} }
.background(Color(.secondarySystemBackground)) // Фон для всей вкладки // .background(Color(.secondarySystemBackground)) // Фон для всей вкладки
} }
} }
struct PostGridItem: View { struct PostGridItem: View {
let post: Post let post: Post
let width: CGFloat // Ширина элемента
private var randomHeight: CGFloat { // Формируем URL для загрузки изображения
CGFloat.random(in: 150...300) private var imageURL: URL? {
// Используем picsum.photos для получения уникального изображения для каждого поста
// Используем реальные размеры для большей вариативности
if let media = post.media.first, let w = media.width, let h = media.height {
return URL(string: "https://picsum.photos/seed/\(post.id.uuidString)/\(w)/\(h)")
}
return URL(string: "https://picsum.photos/seed/\(post.id.uuidString)/400/400")
}
// Вычисляем высоту изображения на основе данных из модели
private var imageHeight: CGFloat {
guard let media = post.media.first,
let mediaWidth = media.width,
let mediaHeight = media.height,
mediaWidth > 0 else {
return width // Возвращаем 1:1, если данных нет
}
let aspectRatio = CGFloat(mediaHeight) / CGFloat(mediaWidth)
return width * aspectRatio
} }
var body: some View { var body: some View {
NavigationLink(destination: PostDetailView(post: post)) {
VStack(alignment: .leading, spacing: 0) { // Убираем отступ между картинкой и текстом VStack(alignment: .leading, spacing: 0) { // Убираем отступ между картинкой и текстом
// 1. Медиа контент // 1. Медиа контент
if let _ = post.media.first { if let url = imageURL {
Image("placeholderPhoto") // Создаем контейнер с четкими границами, чтобы избежать перекрытия
.resizable() Color.clear
.aspectRatio(contentMode: .fill) .frame(width: width, height: imageHeight) // Используем вычисленную высоту
.frame(height: randomHeight) .background(
RemoteImageView(url: url)
.scaledToFill()
)
.clipped()
} }
// Контейнер для текста, который создает эффект "расширения" // Контейнер для текста, который создает эффект "расширения"
@ -72,6 +93,7 @@ struct PostGridItem: View {
HStack { HStack {
Button(action: { Button(action: {
print("account \(post.id)")
// пока ничего не делаем // пока ничего не делаем
}) { }) {
HStack(spacing: 4) { HStack(spacing: 4) {
@ -90,6 +112,7 @@ struct PostGridItem: View {
Spacer() Spacer()
Button(action: { Button(action: {
print("like \(post.id)")
// пока ничего не делаем // пока ничего не делаем
}) { }) {
HStack(spacing: 4) { HStack(spacing: 4) {
@ -111,4 +134,6 @@ struct PostGridItem: View {
.clipped() // Обрезаем дочерние вью по закругленной форме родителя .clipped() // Обрезаем дочерние вью по закругленной форме родителя
.shadow(color: Color.black.opacity(0.1), radius: 5, x: 0, y: 2) .shadow(color: Color.black.opacity(0.1), radius: 5, x: 0, y: 2)
} }
.buttonStyle(PlainButtonStyle()) // Убираем стандартный стиль кнопки, чтобы не влиять на UI
}
} }

View File

@ -30,13 +30,12 @@
1A7940F02DF7B7A3002569DA /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 1A7940F22DF7B7A3002569DA /* Localizable.strings */; }; 1A7940F02DF7B7A3002569DA /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 1A7940F22DF7B7A3002569DA /* Localizable.strings */; };
1A79410C2DF7C81D002569DA /* RegistrationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A79410B2DF7C81D002569DA /* RegistrationView.swift */; }; 1A79410C2DF7C81D002569DA /* RegistrationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A79410B2DF7C81D002569DA /* RegistrationView.swift */; };
1A9B014B2E4BF3CD00887E0B /* NewHomeTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A9B014A2E4BF3CD00887E0B /* NewHomeTab.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 */; }; 1A9B01662E4BFA3600887E0B /* Media.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1A9B01652E4BFA3600887E0B /* Media.xcassets */; };
1A9B016E2E4BFB9000887E0B /* NewHomeTabViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A9B016D2E4BFB9000887E0B /* NewHomeTabViewModel.swift */; }; 1A9B016E2E4BFB9000887E0B /* NewHomeTabViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A9B016D2E4BFB9000887E0B /* NewHomeTabViewModel.swift */; };
1A9B017C2E4C087F00887E0B /* SideMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A9B017B2E4C087F00887E0B /* SideMenuView.swift */; }; 1A9B017C2E4C087F00887E0B /* SideMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A9B017B2E4C087F00887E0B /* SideMenuView.swift */; };
1A9E4FB32E4D6A67002249D6 /* ThemeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A9E4FB22E4D6A67002249D6 /* ThemeManager.swift */; }; 1A9E4FB32E4D6A67002249D6 /* ThemeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A9E4FB22E4D6A67002249D6 /* ThemeManager.swift */; };
1A9E4FD72E4E47EF002249D6 /* RemoteImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A9E4FD62E4E47EF002249D6 /* RemoteImageView.swift */; };
1A9E4FDF2E4E4AA1002249D6 /* ImageCacheManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A9E4FDE2E4E4AA1002249D6 /* ImageCacheManager.swift */; };
1AB4F8CD2E22E341002B6E40 /* AccountShareSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AB4F8CC2E22E341002B6E40 /* AccountShareSheet.swift */; }; 1AB4F8CD2E22E341002B6E40 /* AccountShareSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AB4F8CC2E22E341002B6E40 /* AccountShareSheet.swift */; };
1AB4F8F32E22EC9F002B6E40 /* FollowersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AB4F8F22E22EC9F002B6E40 /* FollowersView.swift */; }; 1AB4F8F32E22EC9F002B6E40 /* FollowersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AB4F8F22E22EC9F002B6E40 /* FollowersView.swift */; };
1AB4F8F72E22ECAC002B6E40 /* FollowingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AB4F8F62E22ECAC002B6E40 /* FollowingView.swift */; }; 1AB4F8F72E22ECAC002B6E40 /* FollowingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AB4F8F62E22ECAC002B6E40 /* FollowingView.swift */; };
@ -85,13 +84,12 @@
1A7940F72DF7B7EC002569DA /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = "<group>"; }; 1A7940F72DF7B7EC002569DA /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = "<group>"; };
1A79410B2DF7C81D002569DA /* RegistrationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegistrationView.swift; sourceTree = "<group>"; }; 1A79410B2DF7C81D002569DA /* RegistrationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegistrationView.swift; sourceTree = "<group>"; };
1A9B014A2E4BF3CD00887E0B /* NewHomeTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewHomeTab.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>"; }; 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>"; }; 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>"; }; 1A9B017B2E4C087F00887E0B /* SideMenuView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SideMenuView.swift; sourceTree = "<group>"; };
1A9E4FB22E4D6A67002249D6 /* ThemeManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThemeManager.swift; sourceTree = "<group>"; }; 1A9E4FB22E4D6A67002249D6 /* ThemeManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThemeManager.swift; sourceTree = "<group>"; };
1A9E4FD62E4E47EF002249D6 /* RemoteImageView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RemoteImageView.swift; sourceTree = "<group>"; };
1A9E4FDE2E4E4AA1002249D6 /* ImageCacheManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageCacheManager.swift; sourceTree = "<group>"; };
1AB4F8CC2E22E341002B6E40 /* AccountShareSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountShareSheet.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>"; }; 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>"; }; 1AB4F8F62E22ECAC002B6E40 /* FollowingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowingView.swift; sourceTree = "<group>"; };
@ -229,6 +227,7 @@
1A7940E52DF7B341002569DA /* Services */ = { 1A7940E52DF7B341002569DA /* Services */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
1A9E4FDE2E4E4AA1002249D6 /* ImageCacheManager.swift */,
1A9E4FB22E4D6A67002249D6 /* ThemeManager.swift */, 1A9E4FB22E4D6A67002249D6 /* ThemeManager.swift */,
1A7940E12DF7B1C5002569DA /* KeychainService.swift */, 1A7940E12DF7B1C5002569DA /* KeychainService.swift */,
); );
@ -247,9 +246,6 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
1A9B01652E4BFA3600887E0B /* Media.xcassets */, 1A9B01652E4BFA3600887E0B /* Media.xcassets */,
1A9B015E2E4BF9C000887E0B /* placeholderPhoto.jpg */,
1A9B015D2E4BF9C000887E0B /* placeholderPhotoSquare.png */,
1A9B01572E4BF50D00887E0B /* placeholderVideo.mp4 */,
); );
path = Media; path = Media;
sourceTree = "<group>"; sourceTree = "<group>";
@ -257,6 +253,7 @@
1AB7F5132E32EBF1003756F3 /* Components */ = { 1AB7F5132E32EBF1003756F3 /* Components */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
1A9E4FD62E4E47EF002249D6 /* RemoteImageView.swift */,
1AB7F5142E32EC1C003756F3 /* RefreshableScrollView.swift */, 1AB7F5142E32EC1C003756F3 /* RefreshableScrollView.swift */,
1AB7F5152E32EC1C003756F3 /* TopBarView.swift */, 1AB7F5152E32EC1C003756F3 /* TopBarView.swift */,
); );
@ -402,12 +399,9 @@
isa = PBXResourcesBuildPhase; isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
1A9B01582E4BF50D00887E0B /* placeholderVideo.mp4 in Resources */,
1A7940912DF77BC3002569DA /* Assets.xcassets in Resources */, 1A7940912DF77BC3002569DA /* Assets.xcassets in Resources */,
1A7940F02DF7B7A3002569DA /* Localizable.strings in Resources */, 1A7940F02DF7B7A3002569DA /* Localizable.strings in Resources */,
1A9B01602E4BF9C000887E0B /* placeholderPhoto.jpg in Resources */,
1A9B01662E4BFA3600887E0B /* Media.xcassets in Resources */, 1A9B01662E4BFA3600887E0B /* Media.xcassets in Resources */,
1A9B015F2E4BF9C000887E0B /* placeholderPhotoSquare.png in Resources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -429,10 +423,12 @@
1AEE5EAB2E21A83200A3DCA3 /* HomeTab.swift in Sources */, 1AEE5EAB2E21A83200A3DCA3 /* HomeTab.swift in Sources */,
1ACE60F82E22F3DC00B37AC5 /* SettingsView.swift in Sources */, 1ACE60F82E22F3DC00B37AC5 /* SettingsView.swift in Sources */,
1AD757CD2E27608C0069C1FD /* PostFeedView.swift in Sources */, 1AD757CD2E27608C0069C1FD /* PostFeedView.swift in Sources */,
1A9E4FD72E4E47EF002249D6 /* RemoteImageView.swift in Sources */,
1A9B017C2E4C087F00887E0B /* SideMenuView.swift in Sources */, 1A9B017C2E4C087F00887E0B /* SideMenuView.swift in Sources */,
1A7940C62DF7A98E002569DA /* ContactsTab.swift in Sources */, 1A7940C62DF7A98E002569DA /* ContactsTab.swift in Sources */,
1ACE61092E22F57100B37AC5 /* AppPreferencesView.swift in Sources */, 1ACE61092E22F57100B37AC5 /* AppPreferencesView.swift in Sources */,
1ACE61052E22F56800B37AC5 /* SecuritySettingsView.swift in Sources */, 1ACE61052E22F56800B37AC5 /* SecuritySettingsView.swift in Sources */,
1A9E4FDF2E4E4AA1002249D6 /* ImageCacheManager.swift in Sources */,
1A9B016E2E4BFB9000887E0B /* NewHomeTabViewModel.swift in Sources */, 1A9B016E2E4BFB9000887E0B /* NewHomeTabViewModel.swift in Sources */,
1A7940E72DF7B5E5002569DA /* SplashScreenView.swift in Sources */, 1A7940E72DF7B5E5002569DA /* SplashScreenView.swift in Sources */,
1A7940B02DF77E26002569DA /* LoginView.swift in Sources */, 1A7940B02DF77E26002569DA /* LoginView.swift in Sources */,