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