diff --git a/Shared/Models/Post.swift b/Shared/Models/Post.swift new file mode 100644 index 0000000..f7d0e77 --- /dev/null +++ b/Shared/Models/Post.swift @@ -0,0 +1,44 @@ +import Foundation + +struct PostIDWrapper: Identifiable { + let id: UUID +} + +struct Post: Identifiable, Codable { + let id: UUID // Уникальный идентификатор поста + let title: String? // Название поста + let description: String? // Описание поста + let media: [MediaItem] // Массив медиафайлов + let mediaOrder: [UUID]? // Порядок отображения медиа + let thumbnailID: UUID? // Превью для видео + let duration: Double? // Длительность видео/аудио + let createdAt: Date // Дата создания + let updatedAt: Date // Дата обновления + let views: Int // Просмотры + let likes: Int // Лайки + let saves: Int // В сохранённом + let commentsCount: Int // Кол-во комментариев + let isLikedByCurrentUser: Bool // Лайк текущим юзером + let isSavedByCurrentUser: Bool // Сохранено текущим юзером + let authorID: String // Автор + let hashtags: [String]? // Хэштеги + let location: String? // Гео + let languageCode: [String]? // Язык + let accessLevel: AccessLevel // Доступ (публичный и т.п.) +} + +enum MediaType: String, Codable { + case photo + case video +} + +struct MediaItem: Identifiable, Codable { + let id: UUID + let type: MediaType +} + +enum AccessLevel: String, Codable { + case `public` + case friends + case archive +} diff --git a/Shared/Network/PostService.swift b/Shared/Network/PostService.swift new file mode 100644 index 0000000..4919df5 --- /dev/null +++ b/Shared/Network/PostService.swift @@ -0,0 +1,142 @@ +import Foundation + +class PostService { + static let shared = PostService() + + private var posts: [Post] = [] + + init() { + generateMockPosts() + } + + private func generateMockPosts() { + guard posts.isEmpty else { return } + + let sampleTitles = [ + "Обзор TikTok UI", + "Мой первый ролик", + "Котик в кадре", + "SwiftUI мастер-класс", + "Анимации и переходы", + "Съёмка с дрона", + "Урок по дизайну", + "Как сделать свайпы", + "Лучший UI 2025", + "Мой первый ролик", + "Котик в кадре", + "SwiftUI мастер-класс", + "Анимации и переходы", + "Съёмка с дрона", + "Урок по дизайну", + "Как сделать свайпы", + "Лучший UI 2025", + "Мой первый ролик", + "Котик в кадре", + "SwiftUI мастер-класс", + "Анимации и переходы", + "Съёмка с дрона", + "Урок по дизайну", + "Как сделать свайпы", + "Лучший UI 2025", + "Мой первый ролик", + "Котик в кадре", + "SwiftUI мастер-класс", + "Анимации и переходы", + "Съёмка с дрона", + "Урок по дизайну", + "Как сделать свайпы", + "Лучший UI 2025", + "Мой первый ролик", + "Котик в кадре", + "SwiftUI мастер-класс", + "Анимации и переходы", + "Съёмка с дрона", + "Урок по дизайну", + "Как сделать свайпы", + "Лучший UI 2025", + "Завершаем проект" + ] + + let sampleDescriptions = [ + "Первый тестовый пост с видео", + "Фейковый контент для ленты", + "Видео с котиком 🐱", + "Интерфейс в стиле TikTok", + "Просто тестирую отображение", + "Код и UI — любовь", + "Видео в реальном времени", + "Интересный UX пример", + "Анимации, переходы, свайпы", + "Фейковый контент для ленты", + "Видео с котиком 🐱", + "Интерфейс в стиле TikTok", + "Просто тестирую отображение", + "Код и UI — любовь", + "Видео в реальном времени", + "Интересный UX пример", + "Анимации, переходы, свайпы", + "Фейковый контент для ленты", + "Видео с котиком 🐱", + "Интерфейс в стиле TikTok", + "Просто тестирую отображение", + "Код и UI — любовь", + "Видео в реальном времени", + "Интересный UX пример", + "Анимации, переходы, свайпы", + "Фейковый контент для ленты", + "Видео с котиком 🐱", + "Интерфейс в стиле TikTok", + "Просто тестирую отображение", + "Код и UI — любовь", + "Видео в реальном времени", + "Интересный UX пример", + "Анимации, переходы, свайпы", + "Вот это да, пост работает!" + ] + + for i in 0..<32 { + let mediaID = UUID() + let thumbID = Bool.random() ? UUID() : nil + let postID = UUID() + + let post = Post( + id: postID, + title: sampleTitles[i], + description: sampleDescriptions[i], + media: [ + MediaItem(id: mediaID, type: .video) + ], + mediaOrder: [mediaID], + thumbnailID: thumbID, + duration: Double.random(in: 15.0...180.0), + createdAt: Date(), + updatedAt: Date(), + views: Int.random(in: 1_000...10_000), + likes: Int.random(in: 100...2_000), + saves: Int.random(in: 10...500), + commentsCount: Int.random(in: 0...100), + isLikedByCurrentUser: Bool.random(), + isSavedByCurrentUser: Bool.random(), + authorID: "user_\(Int.random(in: 1...5))", + hashtags: ["#тест", "#видео", "#swiftui", "#ui"].shuffled().prefix(2).map { $0 }, + location: Bool.random() ? "Москва" : nil, + languageCode: Bool.random() ? ["ru", "en"] : ["ru"], + accessLevel: [.public, .friends, .archive].randomElement()! + ) + posts.append(post) + } + } + + func fetchAllPosts(completion: @escaping ([Post]) -> Void) { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + completion(self.posts) + } + } + + func fetchPost(by id: UUID, completion: @escaping (Post?) -> Void) { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + let result = self.posts.first { $0.id == id } + completion(result) + } + } +} diff --git a/Shared/Views/tab/ChatsTab.swift b/Shared/Views/Tab/ChatsTab.swift similarity index 100% rename from Shared/Views/tab/ChatsTab.swift rename to Shared/Views/Tab/ChatsTab.swift diff --git a/Shared/Views/tab/ContactsTab.swift b/Shared/Views/Tab/ContactsTab.swift similarity index 100% rename from Shared/Views/tab/ContactsTab.swift rename to Shared/Views/Tab/ContactsTab.swift diff --git a/Shared/Views/tab/HomeTab.swift b/Shared/Views/Tab/HomeTab.swift similarity index 100% rename from Shared/Views/tab/HomeTab.swift rename to Shared/Views/Tab/HomeTab.swift diff --git a/Shared/Views/tab/MainView.swift b/Shared/Views/Tab/MainView.swift similarity index 100% rename from Shared/Views/tab/MainView.swift rename to Shared/Views/Tab/MainView.swift diff --git a/Shared/Views/Tab/Profile/AccountShareSheet.swift b/Shared/Views/Tab/Profile/AccountShareSheet.swift new file mode 100644 index 0000000..c1e5396 --- /dev/null +++ b/Shared/Views/Tab/Profile/AccountShareSheet.swift @@ -0,0 +1,25 @@ +// AccountSwitchSheet.swift +import SwiftUI + +struct AccountShareSheet: View { + @Binding var isPresented: Bool + @Binding var selectedAccount: String + let accounts: [String] + + var body: some View { + VStack(spacing: 24) { + Text("Типо qr code") + .font(.title2) + .bold() + .padding(.top, 32) + + Spacer() + + Button("Закрыть") { + isPresented = false + } + .padding() + } + .padding() + } +} diff --git a/Shared/Views/Tab/Profile/FollowersView.swift b/Shared/Views/Tab/Profile/FollowersView.swift new file mode 100644 index 0000000..191452a --- /dev/null +++ b/Shared/Views/Tab/Profile/FollowersView.swift @@ -0,0 +1,13 @@ +import SwiftUI + +struct FollowersView: View { + let followers: [String] + + var body: some View { + List(followers, id: \.self) { user in + Text(user) + } + .navigationTitle("Подписчики") + .navigationBarTitleDisplayMode(.inline) + } +} diff --git a/Shared/Views/Tab/Profile/FollowingView.swift b/Shared/Views/Tab/Profile/FollowingView.swift new file mode 100644 index 0000000..1ddb33c --- /dev/null +++ b/Shared/Views/Tab/Profile/FollowingView.swift @@ -0,0 +1,13 @@ +import SwiftUI + +struct FollowingView: View { + let following: [String] + + var body: some View { + List(following, id: \.self) { user in + Text(user) + } + .navigationTitle("Подписки") + .navigationBarTitleDisplayMode(.inline) + } +} diff --git a/Shared/Views/Tab/Profile/PostDetailView.swift b/Shared/Views/Tab/Profile/PostDetailView.swift new file mode 100644 index 0000000..7360014 --- /dev/null +++ b/Shared/Views/Tab/Profile/PostDetailView.swift @@ -0,0 +1,76 @@ +import SwiftUI + +struct PostDetailView: View { + let postID: UUID + @Environment(\.presentationMode) var presentationMode + @State private var post: Post? = nil + + var body: some View { + ZStack { + if let post = post { + Color.black.ignoresSafeArea() + + VStack { + Spacer() + + VStack(alignment: .leading, spacing: 8) { + if let title = post.title { + Text(title) + .font(.title2) + .fontWeight(.bold) + .foregroundColor(.white) + } + + if let desc = post.description { + Text(desc) + .font(.subheadline) + .foregroundColor(Color.white.opacity(0.8)) + } + + Text("UUID: \(post.id.uuidString)") + .font(.caption2) + .foregroundColor(Color.white.opacity(0.5)) + + HStack(spacing: 12) { + Label("\(post.views)", systemImage: "eye") + Label("\(post.likes)", systemImage: "heart") + Label("\(post.saves)", systemImage: "bookmark") + Label("\(post.commentsCount)", systemImage: "text.bubble") + } + .font(.footnote) + .foregroundColor(Color.white.opacity(0.9)) + } + .padding() + .background(Color.black.opacity(0.4)) + .cornerRadius(12) + .padding(.horizontal) + .padding(.bottom, 32) + } + + VStack { + HStack { + Spacer() + Button(action: { + presentationMode.wrappedValue.dismiss() + }) { + Image(systemName: "xmark.circle.fill") + .font(.system(size: 30)) + .foregroundColor(.white) + .padding() + } + } + Spacer() + } + } else { + ProgressView("Загрузка поста…") + .progressViewStyle(CircularProgressViewStyle(tint: .white)) + .foregroundColor(.white) + .onAppear { + PostService.shared.fetchPost(by: postID) { result in + self.post = result + } + } + } + } + } +} diff --git a/Shared/Views/Tab/Profile/ProfileContentGrid.swift b/Shared/Views/Tab/Profile/ProfileContentGrid.swift new file mode 100644 index 0000000..804e4d4 --- /dev/null +++ b/Shared/Views/Tab/Profile/ProfileContentGrid.swift @@ -0,0 +1,138 @@ +import SwiftUI + + +struct ProfileContentGrid: View { + let isContentLoaded: Bool + let selectedTabIndex: Int + let searchQuery: String + let selectedCategory: String + @State private var allPosts: [Post] = [] + @State private var selectedPostID: PostIDWrapper? = nil + @State private var isLoading = true + + var body: some View { + VStack(alignment: .leading, spacing: 0) { + if isLoading { +// LazyVGrid(columns: Array(repeating: .init(.flexible()), count: 3), spacing: 2) { +// ForEach(0..<100, id: \.self) { _ in +// RoundedRectangle(cornerRadius: 4) +// .fill(Color.gray.opacity(0.15)) +// .aspectRatio(1, contentMode: .fit) +// } +// } +// .padding(.horizontal) +// .redacted(reason: .placeholder) // ⬅️ делает «размытый» стиль + VStack { + ProgressView("Загрузка...") + .padding() + Spacer() + } + .frame(maxWidth: .infinity, minHeight: 300) // 👈 гарантированная высота + } else if filteredPosts.isEmpty { + VStack { + Text("Нет постов") + .foregroundColor(.secondary) + .padding() + } + .frame(maxWidth: .infinity, minHeight: 300) + } else { + LazyVGrid(columns: Array(repeating: .init(.flexible()), count: 3), spacing: 2) { + ForEach(filteredPosts) { post in + Button { + selectedPostID = PostIDWrapper(id: post.id) + } label: { + Rectangle() + .fill(Color.gray.opacity(0.2)) + .aspectRatio(1, contentMode: .fit) + .overlay( + ZStack { + VStack { + HStack { + Text("3 года назад") // Можно заменить на `post.createdAt` + .font(.caption2) + .foregroundColor(.white) + .padding(4) + .background(Color.black.opacity(0.6)) + .cornerRadius(4) + Spacer() + } + Spacer() + } + .padding(4) + + VStack { + Spacer() + HStack { + HStack(spacing: 4) { + Image(systemName: "play.fill") + .font(.caption2) + Text("\(post.views)") + .font(.caption2) + } + .foregroundColor(.white) + .padding(4) + .background(Color.black.opacity(0.6)) + .cornerRadius(4) + Spacer() + } + .padding(4) + } + } + ) + } + .buttonStyle(PlainButtonStyle()) + } + } + .padding(.horizontal) + } + } + .frame(maxWidth: .infinity, alignment: .topLeading) + .onAppear { +// guard isContentLoaded else { return } + + if allPosts.isEmpty { + isLoading = true + PostService.shared.fetchAllPosts { result in + self.allPosts = result + self.isLoading = false + } + } + } + .fullScreenCover(item: $selectedPostID) { wrapper in + PostDetailView(postID: wrapper.id) + } + } + + private var filteredPosts: [Post] { + var result: [Post] + + switch selectedTabIndex { + case 1: + result = allPosts.filter { $0.accessLevel == .archive } + case 2: + result = allPosts.filter { $0.isSavedByCurrentUser } + case 3: + result = allPosts.filter { $0.isLikedByCurrentUser } + default: + result = allPosts + } + + // 🔍 Поиск по названию или описанию + if !searchQuery.isEmpty { + result = result.filter { + ($0.title?.localizedCaseInsensitiveContains(searchQuery) ?? false) || + ($0.description?.localizedCaseInsensitiveContains(searchQuery) ?? false) + } + } + + // 🏷️ Фильтрация по категории (если не "#все") + if selectedCategory != "#все" { + result = result.filter { + $0.hashtags?.contains(where: { $0 == selectedCategory }) ?? false + } + } + + return result + } + +} diff --git a/Shared/Views/Tab/Profile/ProfileContentTabbedGrid.swift b/Shared/Views/Tab/Profile/ProfileContentTabbedGrid.swift new file mode 100644 index 0000000..2d574ba --- /dev/null +++ b/Shared/Views/Tab/Profile/ProfileContentTabbedGrid.swift @@ -0,0 +1,139 @@ +import SwiftUI + +struct ProfileContentTabbedGrid: View { + let isContentLoaded: Bool + @State private var selectedTabIndex = 0 + @State private var selectedCategory = "#все" + @State private var searchQuery = "" + @State private var selectedSort = "По дате" + + var body: some View { + VStack(spacing: 0) { + // Вкладки профиля + HStack(spacing: 32) { + menuTab(index: 0) + tabButton(index: 1, systemIcon: "lock") + tabButton(index: 2, systemIcon: "bookmark") + tabButton(index: 3, systemIcon: "heart") + } + .padding(.vertical, 2) + + // Фильтры + HStack { + if selectedTabIndex == 0 { + Menu { + Button("#все") { selectedCategory = "#все" } + Button("#влог") { selectedCategory = "#влог" } + Button("#игры") { selectedCategory = "#игры" } + } label: { + Label(selectedCategory, systemImage: "tag") + .font(.subheadline) + .padding(8) + .background(Color.gray.opacity(0.2)) + .cornerRadius(8) + } + + TextField("Поиск", text: $searchQuery) + .padding(.horizontal, 10) + .padding(.vertical, 6) + .background(Color.gray.opacity(0.15)) + .cornerRadius(8) + .font(.subheadline) + + Button { + // Создать пост + } label: { + Label("Создать", systemImage: "plus") + .font(.subheadline) + .padding(8) + .background(Color.blue.opacity(0.2)) + .cornerRadius(8) + } + } else { + TextField("Поиск", text: $searchQuery) + .padding(.horizontal, 10) + .padding(.vertical, 6) + .background(Color.gray.opacity(0.15)) + .cornerRadius(8) + .font(.subheadline) + } + } + .padding(.horizontal) + .padding(.vertical, 12) + + // Контент с табами + TabView(selection: $selectedTabIndex) { + ForEach(0..<4) { index in + ProfileContentGrid( + isContentLoaded: isContentLoaded, + selectedTabIndex: index, + searchQuery: searchQuery, + selectedCategory: selectedCategory + ) +// .frame(maxWidth: .infinity, minHeight: 10) + .tag(index) + } + } + .tabViewStyle(PageTabViewStyle(indexDisplayMode: .never)) + } + } + + // MARK: - Вкладка с меню + @ViewBuilder + func menuTab(index: Int) -> some View { + if selectedTabIndex == index { + Menu { + Button("По дате") { selectedSort = "По дате" } + Button("По популярности") { selectedSort = "По популярности" } + } label: { + VStack(spacing: 4) { + HStack(spacing: 4) { + Image(systemName: "rectangle.grid.3x2") + .font(.system(size: 18, weight: .medium)) + .foregroundColor(.primary) + Image(systemName: "chevron.down") + .font(.system(size: 10)) + .foregroundColor(.gray) + } + Rectangle() + .frame(height: 2) + .foregroundColor(.primary) + } + .frame(maxWidth: .infinity) + } + } else { + Button { + selectedTabIndex = index + } label: { + VStack(spacing: 4) { + HStack(spacing: 4) { + Image(systemName: "rectangle.grid.3x2") + .font(.system(size: 18, weight: .medium)) + .foregroundColor(.gray) + } + Rectangle() + .frame(height: 2) + .foregroundColor(.clear) + } + .frame(maxWidth: .infinity) + } + } + } + + // MARK: - Остальные вкладки + @ViewBuilder + func tabButton(index: Int, systemIcon: String) -> some View { + VStack(spacing: 4) { + Image(systemName: systemIcon) + .font(.system(size: 18, weight: .medium)) + .foregroundColor(selectedTabIndex == index ? .primary : .gray) + Rectangle() + .frame(height: 2) + .foregroundColor(selectedTabIndex == index ? .primary : .clear) + } + .frame(maxWidth: .infinity) + .onTapGesture { + selectedTabIndex = index + } + } +} diff --git a/Shared/Views/Tab/Profile/ProfileTab.swift b/Shared/Views/Tab/Profile/ProfileTab.swift new file mode 100644 index 0000000..78195d5 --- /dev/null +++ b/Shared/Views/Tab/Profile/ProfileTab.swift @@ -0,0 +1,147 @@ +import SwiftUI + +struct ProfileTab: View { + @ObservedObject var viewModel: LoginViewModel +// @State private var selectedTabIndex = 0 +// @State private var selectedSort = "По дате" +// @State private var selectedCategory = "#все" +// @State private var searchQuery = "" +// @State private var searchQueryArchive = "" +// @State private var searchQuerySaved = "" +// @State private var searchQueryLiked = "" + @State private var isContentLoaded = true + + @State private var accounts = ["@user1", "@user2", "@user3"] + @State private var selectedAccount = "@user1" + + let followers = ["@alice", "@bob", "@charlie"] + let following = ["@dev", "@design", "@ios"] + + @State private var sheetType: SheetType? = nil + enum SheetType: Identifiable { + case accountShare + + var id: Int { + switch self { + case .accountShare: return 1 + } + } + } + + var body: some View { + NavigationView { + if !isContentLoaded{ + SplashScreenView() + } else { + GeometryReader { geometry in + VStack(spacing: 0) { + ScrollView { + // ───── Шапка профиля (занимает ~50% экрана) ───── + header + .frame(minHeight: geometry.size.height * 0.5) + .frame(maxWidth: .infinity) + .background(Color(.systemBackground)) + + // ───── Скролл с контентом ───── + + ProfileContentTabbedGrid(isContentLoaded: isContentLoaded) + + } + .frame(maxWidth: .infinity) + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + } + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .principal) { + Button(action: { + sheetType = .accountShare + }) { + HStack(spacing: 4) { + Text("custom_user_name") + .font(.headline) + .foregroundColor(.primary) + Image(systemName: "chevron.down") + .font(.subheadline) + .foregroundColor(.gray) + } + } + } + + ToolbarItem(placement: .navigationBarTrailing) { + NavigationLink(destination: SettingsView(viewModel: viewModel)) { + Image(systemName: "wrench") + .imageScale(.large) + } + } + } + .sheet(item: $sheetType) { type in + switch type { + case .accountShare: + AccountShareSheet( + isPresented: Binding( + get: { sheetType != nil }, + set: { if !$0 { sheetType = nil } } + ), + selectedAccount: $selectedAccount, + accounts: accounts + ) + } + } + } + } + .navigationViewStyle(StackNavigationViewStyle()) +// .onAppear { +// if !isContentLoaded { +// DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { +// isContentLoaded = true +// } +// } +// } + } + + // MARK: - Шапка профиля + private var header: some View { + VStack(spacing: 12) { + // Аватар и имя + VStack(spacing: 6) { + Image(systemName: "person.crop.circle.fill") + .resizable() + .frame(width: 72, height: 72) + .foregroundColor(.gray) + + Text(selectedAccount) + .font(.headline) + } + .padding(.top, 16) + + Text("iOS разработчик, делаю интерфейсы, иногда снимаю блоги и делюсь кодом. Всегда рад новым подписчикам и интересным проектам.") + .font(.subheadline) + .foregroundColor(.secondary) + .multilineTextAlignment(.center) + .padding(.horizontal) + + // Статистика + HStack(spacing: 32) { + statView("24", "Посты") + NavigationLink(destination: FollowersView(followers: followers)) { + statView("1.2k", "Подписчики") + } + NavigationLink(destination: FollowingView(following: following)) { + statView("156", "Подписки") + } + } + } + .padding(.horizontal) + } + + // MARK: - Статистика + func statView(_ value: String, _ label: String) -> some View { + VStack { + Text(value) + .font(.headline) + Text(label) + .font(.caption) + } + } +} diff --git a/Shared/Views/Tab/Profile/Settings/AppPreferencesView.swift b/Shared/Views/Tab/Profile/Settings/AppPreferencesView.swift new file mode 100644 index 0000000..2a7b67d --- /dev/null +++ b/Shared/Views/Tab/Profile/Settings/AppPreferencesView.swift @@ -0,0 +1,8 @@ +import SwiftUI + +struct AppPreferencesView: View { + var body: some View { + Text("Настройки приложения") + .navigationTitle("Приложение") + } +} diff --git a/Shared/Views/Tab/Profile/Settings/EditProfileView.swift b/Shared/Views/Tab/Profile/Settings/EditProfileView.swift new file mode 100644 index 0000000..8f869be --- /dev/null +++ b/Shared/Views/Tab/Profile/Settings/EditProfileView.swift @@ -0,0 +1,24 @@ +import SwiftUI + +struct EditProfileView: View { + @State private var displayName = "" + @State private var description = "" + + var body: some View { + Form { + Section(header: Text("Публичная информация")) { + TextField("Отображаемое имя", text: $displayName) + TextField("Описание", text: $description) + } + + Button(action: { + // Действие для сохранения профиля + print("DisplayName: \(displayName)") + print("Description: \(description)") + }) { + Text("Применить") + } + } + .navigationTitle("Редактировать профиль") + } +} diff --git a/Shared/Views/Tab/Profile/Settings/SecuritySettingsView.swift b/Shared/Views/Tab/Profile/Settings/SecuritySettingsView.swift new file mode 100644 index 0000000..2fd6cc3 --- /dev/null +++ b/Shared/Views/Tab/Profile/Settings/SecuritySettingsView.swift @@ -0,0 +1,8 @@ +import SwiftUI + +struct SecuritySettingsView: View { + var body: some View { + Text("Настройки безопасности") + .navigationTitle("Безопасность") + } +} diff --git a/Shared/Views/Tab/Profile/Settings/SettingsView.swift b/Shared/Views/Tab/Profile/Settings/SettingsView.swift new file mode 100644 index 0000000..9e2bd40 --- /dev/null +++ b/Shared/Views/Tab/Profile/Settings/SettingsView.swift @@ -0,0 +1,98 @@ +import SwiftUI + +struct SettingsView: View { + @ObservedObject var viewModel: LoginViewModel + @AppStorage("isDarkMode") private var isDarkMode: Bool = true + + var body: some View { + Form { + // MARK: - Профиль + Section(header: Text("Профиль")) { + NavigationLink(destination: EditProfileView()) { + Label("Мой профиль", systemImage: "person.crop.circle") + } + } + + // MARK: - Безопасность + Section(header: Text("Безопасность")) { + NavigationLink(destination: Text("Заглушка: Сменить пароль")) { + Label("Сменить пароль", systemImage: "key") + } + NavigationLink(destination: Text("Заглушка: Двухфакторная аутентификация")) { + Label("Двухфакторная аутентификация", systemImage: "lock.shield") + } + NavigationLink(destination: Text("Заглушка: Активные сессии")) { + Label("Активные сессии", systemImage: "iphone") + } + } + + // MARK: - Приложение + Section(header: Text("Приложение")) { + Button(action: openLanguageSettings) { + Label("Язык", systemImage: "globe") + } + + Toggle(isOn: $isDarkMode) { + Label("Тёмная тема", systemImage: "moon.fill") + } + + NavigationLink(destination: Text("Заглушка: Хранилище данных")) { + Label("Данные", systemImage: "externaldrive") + } + + NavigationLink(destination: Text("Заглушка: Другие настройки")) { + Label("Другое", systemImage: "ellipsis.circle") + } + } + + // MARK: - Уведомления + Section(header: Text("Уведомления")) { + NavigationLink(destination: Text("Заглушка: Push-уведомления")) { + Label("Push-уведомления", systemImage: "bell") + } + } + + // MARK: - Поддержка + Section(header: Text("Поддержка")) { + NavigationLink(destination: Text("Заглушка: Частые вопросы")) { + Label("Частые вопросы", systemImage: "questionmark.circle") + } + NavigationLink(destination: Text("Заглушка: Обратная связь")) { + Label("Обратная связь", systemImage: "paperplane") + } + } + + // MARK: - О приложении + Section(header: Text("О приложении")) { + VStack(alignment: .leading, spacing: 6) { + Text(AppInfo.text_1) + Text(AppInfo.text_2) + Text(AppInfo.text_3) + } + .font(.footnote) + .foregroundColor(.gray) + .padding(.vertical, 4) + } + + // MARK: - Выход + Section { + Button(action: { + viewModel.logoutCurrentUser() + }) { + HStack { + Image(systemName: "arrow.backward.square") + Text("Выйти из аккаунта") + } + .foregroundColor(.red) + } + } + } + .navigationTitle("Настройки") + } + + private func openLanguageSettings() { + guard let url = URL(string: UIApplication.openSettingsURLString) else { return } + UIApplication.shared.open(url) + } + +} diff --git a/Shared/Views/tab/PublicsTab.swift b/Shared/Views/Tab/PublicsTab.swift similarity index 100% rename from Shared/Views/tab/PublicsTab.swift rename to Shared/Views/Tab/PublicsTab.swift diff --git a/Shared/Views/tab/SearchTab.swift b/Shared/Views/Tab/SearchTab.swift similarity index 100% rename from Shared/Views/tab/SearchTab.swift rename to Shared/Views/Tab/SearchTab.swift diff --git a/Shared/Views/tab/profile/ProfileTab.swift b/Shared/Views/tab/profile/ProfileTab.swift deleted file mode 100644 index cec2dd3..0000000 --- a/Shared/Views/tab/profile/ProfileTab.swift +++ /dev/null @@ -1,297 +0,0 @@ -import SwiftUI - -struct ProfileTab: View { - @ObservedObject var viewModel: LoginViewModel - @State private var showingAccountSwitch = false - @State private var selectedTabIndex = 0 - @State private var selectedSort = "По дате" - @State private var selectedCategory = "#все" - @State private var searchQuery = "" - @State private var searchQueryArchive = "" - @State private var searchQuerySaved = "" - @State private var searchQueryLiked = "" - - var body: some View { - NavigationView { - GeometryReader { geometry in - VStack(spacing: 0) { - ScrollView { - VStack(spacing: 12) { - // Аватар и имя - VStack(spacing: 6) { - Image(systemName: "person.crop.circle.fill") - .resizable() - .frame(width: 72, height: 72) - .foregroundColor(.gray) - - Text("@username") - .font(.headline) - } - .padding(.top, 16) - - // Статистика - HStack(spacing: 32) { - statView("24", "Посты") - statView("1.2k", "Подписчики") - statView("156", "Подписки") - } - - // Вкладки профиля - HStack(spacing: 32) { - menuTab(index: 0) - tabButton(index: 1, systemIcon: "lock") - tabButton(index: 2, systemIcon: "bookmark") - tabButton(index: 3, systemIcon: "heart") - } - .padding(.vertical, 12) - } - .padding(.horizontal) - .frame(width: geometry.size.width) - - if selectedTabIndex == 0 { - HStack { - Menu { - // Здесь список категорий - Button("#все") { selectedCategory = "#все" } - Button("#влог") { selectedCategory = "#влог" } - Button("#игры") { selectedCategory = "#игры" } - Button("#путешествия") { selectedCategory = "#путешествия" } - } label: { - Label(selectedCategory, systemImage: "tag") - .font(.subheadline) - .padding(8) - .background(Color.gray.opacity(0.2)) - .cornerRadius(8) - } - - // Поиск - TextField("Поиск", text: $searchQuery) - .padding(.horizontal, 10) - .padding(.vertical, 6) - .background(Color.gray.opacity(0.15)) - .cornerRadius(8) - .font(.subheadline) - - Button(action: { - // Создание поста - }) { - Label("Создать", systemImage: "plus") - .font(.subheadline) - .padding(8) - .background(Color.blue.opacity(0.2)) - .cornerRadius(8) - } - } - .padding(.horizontal) - } else if selectedTabIndex == 1 { - HStack { - // Поиск - TextField("Поиск", text: $searchQueryArchive) - .padding(.horizontal, 10) - .padding(.vertical, 6) - .background(Color.gray.opacity(0.15)) - .cornerRadius(8) - .font(.subheadline) - } - .padding(.horizontal) - } else if selectedTabIndex == 2 { - HStack { - // Поиск - TextField("Поиск", text: $searchQuerySaved) - .padding(.horizontal, 10) - .padding(.vertical, 6) - .background(Color.gray.opacity(0.15)) - .cornerRadius(8) - .font(.subheadline) - } - .padding(.horizontal) - } else if selectedTabIndex == 3 { - HStack { - // Поиск - TextField("Поиск", text: $searchQueryLiked) - .padding(.horizontal, 10) - .padding(.vertical, 6) - .background(Color.gray.opacity(0.15)) - .cornerRadius(8) - .font(.subheadline) - } - .padding(.horizontal) - } - - - // Контентная часть - TabView(selection: $selectedTabIndex) { - ForEach(0..<4) { index in - LazyVGrid(columns: Array(repeating: .init(.flexible()), count: 3), spacing: 2) { - ForEach(0..<36) { _ in - Rectangle() - .fill(contentColor(for: index)) - .aspectRatio(1, contentMode: .fit) - .overlay( - ZStack { - // Верхний левый угол — дата - VStack { - HStack { - Text("3 года назад") - .font(.caption2) - .foregroundColor(.white) - .padding(4) - .background(Color.black.opacity(0.6)) - .cornerRadius(4) - Spacer() - } - Spacer() - } - .padding(4) - - // Нижний левый угол — просмотры - VStack { - Spacer() - HStack { - HStack(spacing: 4) { - Image(systemName: "play.fill") - .font(.caption2) - Text("1.2k") - .font(.caption2) - } - .foregroundColor(.white) - .padding(4) - .background(Color.black.opacity(0.6)) - .cornerRadius(4) - - Spacer() - } - .padding(4) - } - } - ) - - } - } - - } - } - .tabViewStyle(PageTabViewStyle(indexDisplayMode: .never)) -// .frame(height: geometry.size.width * 1.2) // Динамическая высота - } - } - } - .navigationBarTitleDisplayMode(.inline) - .toolbar { - ToolbarItem(placement: .principal) { - Button(action: { showingAccountSwitch.toggle() }) { - HStack(spacing: 4) { - Text("custom_user_name") - .font(.headline) - .foregroundColor(.primary) - Image(systemName: "chevron.down") - .font(.subheadline) - .foregroundColor(.gray) - } - } - } - - // Левый верх — ручка (редактирование) - ToolbarItem(placement: .navigationBarLeading) { - Button(action: { - // действие редактирования профиля - }) { - Image(systemName: "paintbrush") - .imageScale(.large) - } - } - - ToolbarItem(placement: .navigationBarTrailing) { - Button(action: { - // перейти к настройкам - }) { - Image(systemName: "wrench") - .imageScale(.large) - } - } - } - .sheet(isPresented: $showingAccountSwitch) { - VStack { - Text("Выбор аккаунта") - .font(.title) - .padding() - Button("Закрыть") { - showingAccountSwitch = false - } - .padding() - } - } - } - } - - // MARK: - Статистика - func statView(_ value: String, _ label: String) -> some View { - VStack { - Text(value) - .font(.headline) - Text(label) - .font(.caption) - } - } - - // MARK: - Вкладка с меню - @ViewBuilder - func menuTab(index: Int) -> some View { - Menu { - if selectedTabIndex == index { - Button("По дате") { selectedSort = "По дате" } - Button("По популярности") { selectedSort = "По популярности" } - } - } label: { - VStack(spacing: 4) { - HStack(spacing: 4) { - Image(systemName: "rectangle.grid.3x2") - .font(.system(size: 18, weight: .medium)) - .foregroundColor(.primary) - - // Показываем стрелку вниз только если вкладка активна - if selectedTabIndex == index { - Image(systemName: "chevron.down") - .font(.system(size: 10)) - .foregroundColor(.gray) - } - } - Rectangle() - .frame(height: 2) - .foregroundColor(selectedTabIndex == index ? .primary : .clear) - } - .frame(maxWidth: .infinity) - } - .onTapGesture { - selectedTabIndex = index - } - } - - // MARK: - Остальные вкладки - @ViewBuilder - func tabButton(index: Int, systemIcon: String) -> some View { - VStack(spacing: 4) { - Image(systemName: systemIcon) - .font(.system(size: 18, weight: .medium)) - .foregroundColor(selectedTabIndex == index ? .primary : .gray) - Rectangle() - .frame(height: 2) - .foregroundColor(selectedTabIndex == index ? .primary : .clear) - } - .frame(maxWidth: .infinity) - .onTapGesture { - selectedTabIndex = index - } - } - - // MARK: - Цвет контента - func contentColor(for tab: Int) -> Color { - switch tab { - case 0: return Color.gray.opacity(0.3) - case 1: return Color.blue.opacity(0.3) - case 2: return Color.green.opacity(0.3) - case 3: return Color.red.opacity(0.3) - default: return Color.gray.opacity(0.3) - } - } -} diff --git a/Shared/config.swift b/Shared/config.swift index eed256e..cee095c 100644 --- a/Shared/config.swift +++ b/Shared/config.swift @@ -10,3 +10,9 @@ struct AppConfig { static let APP_BUILD = "freestore" static let APP_VERSION = "0.1" } + +struct AppInfo { + static let text_1 = "\(NSLocalizedString("profile_down_text_1", comment: "")) yobble" + static let text_2 = "\(NSLocalizedString("profile_down_text_2", comment: "")) 0.1test" + static let text_3 = "\(NSLocalizedString("profile_down_text_3", comment: ""))2025" +} diff --git a/yobble.xcodeproj/project.pbxproj b/yobble.xcodeproj/project.pbxproj index 3d26012..b42ece3 100644 --- a/yobble.xcodeproj/project.pbxproj +++ b/yobble.xcodeproj/project.pbxproj @@ -27,6 +27,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 */; }; + 1AB4F8CD2E22E341002B6E40 /* AccountShareSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AB4F8CC2E22E341002B6E40 /* AccountShareSheet.swift */; }; + 1AB4F8F32E22EC9F002B6E40 /* FollowersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AB4F8F22E22EC9F002B6E40 /* FollowersView.swift */; }; + 1AB4F8F72E22ECAC002B6E40 /* FollowingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AB4F8F62E22ECAC002B6E40 /* FollowingView.swift */; }; + 1ACE60F82E22F3DC00B37AC5 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ACE60F72E22F3DC00B37AC5 /* SettingsView.swift */; }; + 1ACE61012E22F55C00B37AC5 /* EditProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ACE61002E22F55C00B37AC5 /* EditProfileView.swift */; }; + 1ACE61052E22F56800B37AC5 /* SecuritySettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ACE61042E22F56800B37AC5 /* SecuritySettingsView.swift */; }; + 1ACE61092E22F57100B37AC5 /* AppPreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ACE61082E22F57100B37AC5 /* AppPreferencesView.swift */; }; + 1ACE61152E22FE2000B37AC5 /* PostDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ACE61142E22FE2000B37AC5 /* PostDetailView.swift */; }; + 1ACE61192E22FF1400B37AC5 /* Post.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ACE61182E22FF1400B37AC5 /* Post.swift */; }; + 1ACE61212E22FFD000B37AC5 /* ProfileContentTabbedGrid.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ACE61202E22FFD000B37AC5 /* ProfileContentTabbedGrid.swift */; }; + 1AE587052E23264800254F06 /* PostService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AE587042E23264800254F06 /* PostService.swift */; }; + 1AE587252E23337000254F06 /* ProfileContentGrid.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AE587242E23337000254F06 /* ProfileContentGrid.swift */; }; 1AEE5EAB2E21A83200A3DCA3 /* HomeTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AEE5EAA2E21A83200A3DCA3 /* HomeTab.swift */; }; 1AEE5EAF2E21A84500A3DCA3 /* PublicsTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AEE5EAE2E21A84500A3DCA3 /* PublicsTab.swift */; }; 1AEE5EB32E21A85800A3DCA3 /* SearchTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AEE5EB22E21A85800A3DCA3 /* SearchTab.swift */; }; @@ -57,6 +69,18 @@ 1A7940F12DF7B7A3002569DA /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; 1A7940F72DF7B7EC002569DA /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = ""; }; 1A79410B2DF7C81D002569DA /* RegistrationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegistrationView.swift; sourceTree = ""; }; + 1AB4F8CC2E22E341002B6E40 /* AccountShareSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountShareSheet.swift; sourceTree = ""; }; + 1AB4F8F22E22EC9F002B6E40 /* FollowersView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowersView.swift; sourceTree = ""; }; + 1AB4F8F62E22ECAC002B6E40 /* FollowingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowingView.swift; sourceTree = ""; }; + 1ACE60F72E22F3DC00B37AC5 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; + 1ACE61002E22F55C00B37AC5 /* EditProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditProfileView.swift; sourceTree = ""; }; + 1ACE61042E22F56800B37AC5 /* SecuritySettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecuritySettingsView.swift; sourceTree = ""; }; + 1ACE61082E22F57100B37AC5 /* AppPreferencesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppPreferencesView.swift; sourceTree = ""; }; + 1ACE61142E22FE2000B37AC5 /* PostDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostDetailView.swift; sourceTree = ""; }; + 1ACE61182E22FF1400B37AC5 /* Post.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Post.swift; sourceTree = ""; }; + 1ACE61202E22FFD000B37AC5 /* ProfileContentTabbedGrid.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileContentTabbedGrid.swift; sourceTree = ""; }; + 1AE587042E23264800254F06 /* PostService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostService.swift; sourceTree = ""; }; + 1AE587242E23337000254F06 /* ProfileContentGrid.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileContentGrid.swift; sourceTree = ""; }; 1AEE5EAA2E21A83200A3DCA3 /* HomeTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeTab.swift; sourceTree = ""; }; 1AEE5EAE2E21A84500A3DCA3 /* PublicsTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublicsTab.swift; sourceTree = ""; }; 1AEE5EB22E21A85800A3DCA3 /* SearchTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchTab.swift; sourceTree = ""; }; @@ -135,7 +159,7 @@ 1A79409D2DF77DB5002569DA /* Views */ = { isa = PBXGroup; children = ( - 1AEE5EA92E21A4FC00A3DCA3 /* tab */, + 1AEE5EA92E21A4FC00A3DCA3 /* Tab */, 1AEE5EA62E2194CD00A3DCA3 /* contacts */, 1AEE5EA52E21947B00A3DCA3 /* login */, 1A79407B2DF77BC2002569DA /* ContentView.swift */, @@ -156,6 +180,7 @@ 1A79409F2DF77DC4002569DA /* Models */ = { isa = PBXGroup; children = ( + 1ACE61182E22FF1400B37AC5 /* Post.swift */, 1A7940A52DF77DF5002569DA /* User.swift */, ); path = Models; @@ -166,6 +191,7 @@ children = ( 1A7940A12DF77DE9002569DA /* AuthService.swift */, 1A0276022DF909F900D8BC53 /* refreshtokenex.swift */, + 1AE587042E23264800254F06 /* PostService.swift */, ); path = Network; sourceTree = ""; @@ -186,6 +212,17 @@ path = Resources; sourceTree = ""; }; + 1ACE60FF2E22F54700B37AC5 /* Settings */ = { + isa = PBXGroup; + children = ( + 1ACE60F72E22F3DC00B37AC5 /* SettingsView.swift */, + 1ACE61002E22F55C00B37AC5 /* EditProfileView.swift */, + 1ACE61042E22F56800B37AC5 /* SecuritySettingsView.swift */, + 1ACE61082E22F57100B37AC5 /* AppPreferencesView.swift */, + ); + path = Settings; + sourceTree = ""; + }; 1AEE5EA52E21947B00A3DCA3 /* login */ = { isa = PBXGroup; children = ( @@ -202,10 +239,10 @@ path = contacts; sourceTree = ""; }; - 1AEE5EA92E21A4FC00A3DCA3 /* tab */ = { + 1AEE5EA92E21A4FC00A3DCA3 /* Tab */ = { isa = PBXGroup; children = ( - 1AEE5ECC2E21C9D100A3DCA3 /* profile */, + 1AEE5ECC2E21C9D100A3DCA3 /* Profile */, 1A7940C52DF7A98E002569DA /* ContactsTab.swift */, 1A7940C92DF7A99B002569DA /* ChatsTab.swift */, 1A7940B52DF77F21002569DA /* MainView.swift */, @@ -213,15 +250,22 @@ 1AEE5EAE2E21A84500A3DCA3 /* PublicsTab.swift */, 1AEE5EB22E21A85800A3DCA3 /* SearchTab.swift */, ); - path = tab; + path = Tab; sourceTree = ""; }; - 1AEE5ECC2E21C9D100A3DCA3 /* profile */ = { + 1AEE5ECC2E21C9D100A3DCA3 /* Profile */ = { isa = PBXGroup; children = ( + 1ACE60FF2E22F54700B37AC5 /* Settings */, 1A7940CD2DF7A9AA002569DA /* ProfileTab.swift */, + 1AB4F8CC2E22E341002B6E40 /* AccountShareSheet.swift */, + 1AE587242E23337000254F06 /* ProfileContentGrid.swift */, + 1ACE61202E22FFD000B37AC5 /* ProfileContentTabbedGrid.swift */, + 1AB4F8F22E22EC9F002B6E40 /* FollowersView.swift */, + 1AB4F8F62E22ECAC002B6E40 /* FollowingView.swift */, + 1ACE61142E22FE2000B37AC5 /* PostDetailView.swift */, ); - path = profile; + path = Profile; sourceTree = ""; }; /* End PBXGroup section */ @@ -324,23 +368,35 @@ buildActionMask = 2147483647; files = ( 1AEE5EAB2E21A83200A3DCA3 /* HomeTab.swift in Sources */, + 1ACE60F82E22F3DC00B37AC5 /* SettingsView.swift in Sources */, 1A7940C62DF7A98E002569DA /* ContactsTab.swift in Sources */, + 1ACE61092E22F57100B37AC5 /* AppPreferencesView.swift in Sources */, + 1ACE61052E22F56800B37AC5 /* SecuritySettingsView.swift in Sources */, 1A7940E72DF7B5E5002569DA /* SplashScreenView.swift in Sources */, 1A7940B02DF77E26002569DA /* LoginView.swift in Sources */, 1A79410C2DF7C81D002569DA /* RegistrationView.swift in Sources */, 1A0276112DF9247000D8BC53 /* CustomTextField.swift in Sources */, 1A7940CE2DF7A9AA002569DA /* ProfileTab.swift in Sources */, + 1ACE61212E22FFD000B37AC5 /* ProfileContentTabbedGrid.swift in Sources */, + 1AB4F8F72E22ECAC002B6E40 /* FollowingView.swift in Sources */, 1A7940DE2DF7B0D7002569DA /* config.swift in Sources */, + 1AE587252E23337000254F06 /* ProfileContentGrid.swift in Sources */, 1A0276032DF909F900D8BC53 /* refreshtokenex.swift in Sources */, 1A7940B62DF77F21002569DA /* MainView.swift in Sources */, 1A7940E22DF7B1C5002569DA /* KeychainService.swift in Sources */, 1A7940A62DF77DF5002569DA /* User.swift in Sources */, 1A7940A22DF77DE9002569DA /* AuthService.swift in Sources */, 1AEE5EB32E21A85800A3DCA3 /* SearchTab.swift in Sources */, + 1ACE61152E22FE2000B37AC5 /* PostDetailView.swift in Sources */, + 1ACE61192E22FF1400B37AC5 /* Post.swift in Sources */, 1A79408F2DF77BC3002569DA /* ContentView.swift in Sources */, + 1AB4F8CD2E22E341002B6E40 /* AccountShareSheet.swift in Sources */, + 1ACE61012E22F55C00B37AC5 /* EditProfileView.swift in Sources */, 1AEE5EAF2E21A84500A3DCA3 /* PublicsTab.swift in Sources */, + 1AE587052E23264800254F06 /* PostService.swift in Sources */, 1A7940CA2DF7A99B002569DA /* ChatsTab.swift in Sources */, 1A7940AA2DF77E05002569DA /* LoginViewModel.swift in Sources */, + 1AB4F8F32E22EC9F002B6E40 /* FollowersView.swift in Sources */, 1A79408D2DF77BC3002569DA /* yobbleApp.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/yobble.xcodeproj/xcuserdata/cheykrym.xcuserdatad/xcschemes/xcschememanagement.plist b/yobble.xcodeproj/xcuserdata/cheykrym.xcuserdatad/xcschemes/xcschememanagement.plist index 6cdd854..2de711f 100644 --- a/yobble.xcodeproj/xcuserdata/cheykrym.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/yobble.xcodeproj/xcuserdata/cheykrym.xcuserdatad/xcschemes/xcschememanagement.plist @@ -14,6 +14,16 @@ orderHint 0 + yobble (iOS).xcscheme_^#shared#^_ + + orderHint + 0 + + yobble (macOS).xcscheme_^#shared#^_ + + orderHint + 1 +