Initial commit
This commit is contained in:
		
							parent
							
								
									03590ca86f
								
							
						
					
					
						commit
						f448fe77f4
					
				
							
								
								
									
										8
									
								
								Shared/Models/User.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								Shared/Models/User.swift
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,8 @@
 | 
			
		||||
//
 | 
			
		||||
//  User.swift
 | 
			
		||||
//  volnahub (iOS)
 | 
			
		||||
//
 | 
			
		||||
//  Created by cheykrym on 09/06/2025.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
import Foundation
 | 
			
		||||
							
								
								
									
										29
									
								
								Shared/Network/AuthService.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								Shared/Network/AuthService.swift
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,29 @@
 | 
			
		||||
//
 | 
			
		||||
//  AuthService.swift
 | 
			
		||||
//  VolnahubApp
 | 
			
		||||
//
 | 
			
		||||
//  Created by cheykrym on 09/06/2025.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
import Foundation
 | 
			
		||||
 | 
			
		||||
class AuthService {
 | 
			
		||||
    func autoLogin(completion: @escaping (Bool) -> Void) {
 | 
			
		||||
        // Симуляция проверки токена
 | 
			
		||||
        DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
 | 
			
		||||
            let success = false  // Пока всегда неуспешно, для теста
 | 
			
		||||
            completion(success)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    func login(username: String, password: String, completion: @escaping (Bool, String?) -> Void) {
 | 
			
		||||
        // Симуляция запроса
 | 
			
		||||
        DispatchQueue.global().asyncAfter(deadline: .now() + 2) {
 | 
			
		||||
            if username == "test" && password == "123123" {
 | 
			
		||||
                completion(true, nil)
 | 
			
		||||
            } else {
 | 
			
		||||
                completion(false, "Неверные учетные данные.")
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										7
									
								
								Shared/Resources/en.lproj/Localizable.strings
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								Shared/Resources/en.lproj/Localizable.strings
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,7 @@
 | 
			
		||||
/* 
 | 
			
		||||
  Localizable.strings
 | 
			
		||||
  volnahub
 | 
			
		||||
 | 
			
		||||
  Created by cheykrym on 10/06/2025.
 | 
			
		||||
  
 | 
			
		||||
*/
 | 
			
		||||
							
								
								
									
										14
									
								
								Shared/Resources/ru.lproj/Localizable.strings
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								Shared/Resources/ru.lproj/Localizable.strings
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,14 @@
 | 
			
		||||
/* 
 | 
			
		||||
  Localizable.strings
 | 
			
		||||
  volnahub
 | 
			
		||||
 | 
			
		||||
  Created by cheykrym on 10/06/2025.
 | 
			
		||||
  
 | 
			
		||||
*/
 | 
			
		||||
"loading_placeholder" = "Загрузка...";
 | 
			
		||||
"LoginView_change_language" = "Язык";
 | 
			
		||||
"LoginView_title" = "Вход";
 | 
			
		||||
"LoginView_login" = "Логин";
 | 
			
		||||
"LoginView_password" = "Пароль";
 | 
			
		||||
"LoginView_button_login" = "Войти";
 | 
			
		||||
"LoginView_button_register" = "Регистрация";
 | 
			
		||||
							
								
								
									
										113
									
								
								Shared/Services/KeychainService.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								Shared/Services/KeychainService.swift
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,113 @@
 | 
			
		||||
import Foundation
 | 
			
		||||
import Security
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
//let username = "user1"
 | 
			
		||||
 | 
			
		||||
// Сохраняем токены
 | 
			
		||||
//KeychainService.shared.save("access_token_value", forKey: "access_token", service: username)
 | 
			
		||||
//KeychainService.shared.save("refresh_token_value", forKey: "refresh_token", service: username)
 | 
			
		||||
 | 
			
		||||
// Получаем токены
 | 
			
		||||
//let accessToken = KeychainService.shared.get(forKey: "access_token", service: username)
 | 
			
		||||
//let refreshToken = KeychainService.shared.get(forKey: "refresh_token", service: username)
 | 
			
		||||
 | 
			
		||||
// получение всех пользователей
 | 
			
		||||
//let users = KeychainService.shared.getAllServices()
 | 
			
		||||
//print("Все пользователи: \(users)")
 | 
			
		||||
 | 
			
		||||
// Удаление всех пользователей
 | 
			
		||||
//KeychainService.shared.deleteAll()
 | 
			
		||||
 | 
			
		||||
// удаление по одному
 | 
			
		||||
//KeychainService.shared.delete(forKey: "access_token", service: username)
 | 
			
		||||
//KeychainService.shared.delete(forKey: "refresh_token", service: username)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class KeychainService {
 | 
			
		||||
    
 | 
			
		||||
    static let shared = KeychainService()
 | 
			
		||||
    
 | 
			
		||||
    private init() {}
 | 
			
		||||
    
 | 
			
		||||
    func save(_ value: String, forKey key: String, service: String) {
 | 
			
		||||
        guard let data = value.data(using: .utf8) else { return }
 | 
			
		||||
        
 | 
			
		||||
        let query: [String: Any] = [
 | 
			
		||||
            kSecClass as String: kSecClassGenericPassword,
 | 
			
		||||
            kSecAttrService as String: service, // ключ группировки
 | 
			
		||||
            kSecAttrAccount as String: key,
 | 
			
		||||
            kSecValueData as String: data
 | 
			
		||||
        ]
 | 
			
		||||
        
 | 
			
		||||
        SecItemDelete(query as CFDictionary)
 | 
			
		||||
        let status = SecItemAdd(query as CFDictionary, nil)
 | 
			
		||||
        if status != errSecSuccess {
 | 
			
		||||
            print("Error saving to Keychain: \(status)")
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    func get(forKey key: String, service: String) -> String? {
 | 
			
		||||
        let query: [String: Any] = [
 | 
			
		||||
            kSecClass as String: kSecClassGenericPassword,
 | 
			
		||||
            kSecAttrService as String: service,
 | 
			
		||||
            kSecAttrAccount as String: key,
 | 
			
		||||
            kSecReturnData as String: true,
 | 
			
		||||
            kSecMatchLimit as String: kSecMatchLimitOne
 | 
			
		||||
        ]
 | 
			
		||||
        
 | 
			
		||||
        var dataTypeRef: AnyObject?
 | 
			
		||||
        let status = SecItemCopyMatching(query as CFDictionary, &dataTypeRef)
 | 
			
		||||
        if status == errSecSuccess,
 | 
			
		||||
           let data = dataTypeRef as? Data,
 | 
			
		||||
           let value = String(data: data, encoding: .utf8) {
 | 
			
		||||
            return value
 | 
			
		||||
        }
 | 
			
		||||
        return nil
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    func getAllServices() -> [String] {
 | 
			
		||||
        let query: [String: Any] = [
 | 
			
		||||
            kSecClass as String: kSecClassGenericPassword,
 | 
			
		||||
            kSecReturnAttributes as String: true,
 | 
			
		||||
            kSecMatchLimit as String: kSecMatchLimitAll
 | 
			
		||||
        ]
 | 
			
		||||
        
 | 
			
		||||
        var result: AnyObject?
 | 
			
		||||
        let status = SecItemCopyMatching(query as CFDictionary, &result)
 | 
			
		||||
        
 | 
			
		||||
        guard status == errSecSuccess, let items = result as? [[String: Any]] else {
 | 
			
		||||
            return []
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Собираем все уникальные service (username)
 | 
			
		||||
        var services = Set<String>()
 | 
			
		||||
        for item in items {
 | 
			
		||||
            if let service = item[kSecAttrService as String] as? String {
 | 
			
		||||
                services.insert(service)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        return Array(services)
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    func delete(forKey key: String, service: String) {
 | 
			
		||||
        let query: [String: Any] = [
 | 
			
		||||
            kSecClass as String: kSecClassGenericPassword,
 | 
			
		||||
            kSecAttrService as String: service,
 | 
			
		||||
            kSecAttrAccount as String: key
 | 
			
		||||
        ]
 | 
			
		||||
        SecItemDelete(query as CFDictionary)
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /// Удалить все записи Keychain, сохранённые этим приложением
 | 
			
		||||
    func deleteAll() {
 | 
			
		||||
        let query: [String: Any] = [
 | 
			
		||||
            kSecClass as String: kSecClassGenericPassword
 | 
			
		||||
        ]
 | 
			
		||||
        let status = SecItemDelete(query as CFDictionary)
 | 
			
		||||
        if status != errSecSuccess && status != errSecItemNotFound {
 | 
			
		||||
            print("Error deleting all Keychain items: \(status)")
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										59
									
								
								Shared/ViewModels/LoginViewModel.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								Shared/ViewModels/LoginViewModel.swift
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,59 @@
 | 
			
		||||
//
 | 
			
		||||
//  LoginViewModel.swift
 | 
			
		||||
//  VolnahubApp
 | 
			
		||||
//
 | 
			
		||||
//  Created by cheykrym on 09/06/2025.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
import Foundation
 | 
			
		||||
import Combine
 | 
			
		||||
 | 
			
		||||
class LoginViewModel: ObservableObject {
 | 
			
		||||
    @Published var username: String = ""
 | 
			
		||||
    @Published var password: String = ""
 | 
			
		||||
    @Published var isLoading: Bool = true    // сразу true, чтобы вызывался автологин
 | 
			
		||||
    @Published var showError: Bool = false
 | 
			
		||||
    @Published var errorMessage: String = ""
 | 
			
		||||
    @Published var isLoggedIn: Bool = false
 | 
			
		||||
 | 
			
		||||
    private let authService = AuthService()
 | 
			
		||||
    
 | 
			
		||||
    init() {
 | 
			
		||||
        autoLogin()
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    func autoLogin() {
 | 
			
		||||
        authService.autoLogin { [weak self] success in
 | 
			
		||||
            DispatchQueue.main.async {
 | 
			
		||||
                self?.isLoading = false
 | 
			
		||||
                if success {
 | 
			
		||||
                    self?.isLoggedIn = true
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    func login() {
 | 
			
		||||
        isLoading = true
 | 
			
		||||
        showError = false
 | 
			
		||||
        
 | 
			
		||||
        authService.login(username: username, password: password) { [weak self] success, error in
 | 
			
		||||
            DispatchQueue.main.async {
 | 
			
		||||
                self?.isLoading = false
 | 
			
		||||
                if success {
 | 
			
		||||
                    self?.isLoggedIn = true
 | 
			
		||||
                } else {
 | 
			
		||||
                    self?.errorMessage = error ?? "Неизвестная ошибка"
 | 
			
		||||
                    self?.showError = true
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    func logout() {
 | 
			
		||||
        username = ""
 | 
			
		||||
        password = ""
 | 
			
		||||
        isLoggedIn = false
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										24
									
								
								Shared/Views/ChatsTab.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								Shared/Views/ChatsTab.swift
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,24 @@
 | 
			
		||||
//
 | 
			
		||||
//  ChatsTab.swift
 | 
			
		||||
//  VolnahubApp
 | 
			
		||||
//
 | 
			
		||||
//  Created by cheykrym on 09/06/2025.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
import SwiftUI
 | 
			
		||||
 | 
			
		||||
struct ChatsTab: View {
 | 
			
		||||
    var body: some View {
 | 
			
		||||
        NavigationView {
 | 
			
		||||
            VStack {
 | 
			
		||||
                Text("Чаты")
 | 
			
		||||
                    .font(.largeTitle)
 | 
			
		||||
                    .bold()
 | 
			
		||||
                    .padding()
 | 
			
		||||
                
 | 
			
		||||
                Spacer()
 | 
			
		||||
            }
 | 
			
		||||
            .navigationTitle("Чаты")
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										24
									
								
								Shared/Views/ContactsTab.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								Shared/Views/ContactsTab.swift
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,24 @@
 | 
			
		||||
//
 | 
			
		||||
//  ContactsTab.swift
 | 
			
		||||
//  VolnahubApp
 | 
			
		||||
//
 | 
			
		||||
//  Created by cheykrym on 09/06/2025.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
import SwiftUI
 | 
			
		||||
 | 
			
		||||
struct ContactsTab: View {
 | 
			
		||||
    var body: some View {
 | 
			
		||||
        NavigationView {
 | 
			
		||||
            VStack {
 | 
			
		||||
                Text("Контакты")
 | 
			
		||||
                    .font(.largeTitle)
 | 
			
		||||
                    .bold()
 | 
			
		||||
                    .padding()
 | 
			
		||||
                
 | 
			
		||||
                Spacer()
 | 
			
		||||
            }
 | 
			
		||||
            .navigationTitle("Контакты")
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										153
									
								
								Shared/Views/LoginView.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										153
									
								
								Shared/Views/LoginView.swift
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,153 @@
 | 
			
		||||
//
 | 
			
		||||
//  LoginView.swift
 | 
			
		||||
//  VolnahubApp
 | 
			
		||||
//
 | 
			
		||||
//  Created by cheykrym on 09/06/2025.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
import SwiftUI
 | 
			
		||||
 | 
			
		||||
struct LoginView: View {
 | 
			
		||||
    @ObservedObject var viewModel: LoginViewModel
 | 
			
		||||
    @AppStorage("isDarkMode") private var isDarkMode: Bool = true
 | 
			
		||||
    
 | 
			
		||||
    @State private var isShowingRegistration = false
 | 
			
		||||
 | 
			
		||||
    private var isUsernameValid: Bool {
 | 
			
		||||
        let pattern = "^[A-Za-z0-9_]{3,32}$"
 | 
			
		||||
        return viewModel.username.range(of: pattern, options: .regularExpression) != nil
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private var isPasswordValid: Bool {
 | 
			
		||||
        return viewModel.password.count >= 6 && viewModel.password.count <= 32
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    var body: some View {
 | 
			
		||||
 | 
			
		||||
        VStack {
 | 
			
		||||
            HStack {
 | 
			
		||||
 | 
			
		||||
                Button(action: openLanguageSettings) {
 | 
			
		||||
                    Text("🌍 " + NSLocalizedString("LoginView_change_language", comment: ""))
 | 
			
		||||
                        .padding()
 | 
			
		||||
                }
 | 
			
		||||
                Spacer()
 | 
			
		||||
                Button(action: toggleTheme) {
 | 
			
		||||
                    Image(systemName: isDarkMode ? "moon.fill" : "sun.max.fill")
 | 
			
		||||
                        .padding()
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            Spacer()
 | 
			
		||||
 | 
			
		||||
            
 | 
			
		||||
//            Text(NSLocalizedString("LoginView_title", comment: ""))
 | 
			
		||||
//                .font(.largeTitle)
 | 
			
		||||
//                .bold()
 | 
			
		||||
 | 
			
		||||
            TextField(NSLocalizedString("LoginView_login", comment: ""), text: $viewModel.username)
 | 
			
		||||
                .padding()
 | 
			
		||||
                .background(Color(.secondarySystemBackground))
 | 
			
		||||
                .cornerRadius(8)
 | 
			
		||||
                .autocapitalization(.none)
 | 
			
		||||
                .disableAutocorrection(true)
 | 
			
		||||
                .onChange(of: viewModel.username) { newValue in
 | 
			
		||||
                    if newValue.count > 32 {
 | 
			
		||||
                        viewModel.username = String(newValue.prefix(32))
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
            // Показываем ошибку для логина
 | 
			
		||||
            if !isUsernameValid && !viewModel.username.isEmpty {
 | 
			
		||||
                Text(NSLocalizedString("LoginView_error_username_invalid", comment: "Неверный логин"))
 | 
			
		||||
                    .foregroundColor(.red)
 | 
			
		||||
                    .font(.caption)
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Показываем поле пароля (даже если оно невалидное) только если логин корректен
 | 
			
		||||
            if isUsernameValid {
 | 
			
		||||
                SecureField(NSLocalizedString("LoginView_password", comment: ""), text: $viewModel.password)
 | 
			
		||||
                    .padding()
 | 
			
		||||
                    .background(Color(.secondarySystemBackground))
 | 
			
		||||
                    .cornerRadius(8)
 | 
			
		||||
                    .autocapitalization(.none)
 | 
			
		||||
                    .onChange(of: viewModel.password) { newValue in
 | 
			
		||||
                        if newValue.count > 32 {
 | 
			
		||||
                            viewModel.password = String(newValue.prefix(32))
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                // Показываем ошибку для пароля
 | 
			
		||||
                if !isPasswordValid && !viewModel.password.isEmpty {
 | 
			
		||||
                    Text(NSLocalizedString("LoginView_error_password_invalid", comment: "Неверный пароль"))
 | 
			
		||||
                        .foregroundColor(.red)
 | 
			
		||||
                        .font(.caption)
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            if isUsernameValid && isPasswordValid {
 | 
			
		||||
                Button(action: {
 | 
			
		||||
                    viewModel.login()
 | 
			
		||||
                }) {
 | 
			
		||||
                    if viewModel.isLoading {
 | 
			
		||||
                        ProgressView()
 | 
			
		||||
                            .progressViewStyle(CircularProgressViewStyle())
 | 
			
		||||
                            .padding()
 | 
			
		||||
                            .frame(maxWidth: .infinity)
 | 
			
		||||
                            .background(Color.gray.opacity(0.6))
 | 
			
		||||
                            .cornerRadius(8)
 | 
			
		||||
                    } else {
 | 
			
		||||
                        Text(NSLocalizedString("LoginView_button_login", comment: ""))
 | 
			
		||||
                            .foregroundColor(.white)
 | 
			
		||||
                            .padding()
 | 
			
		||||
                            .frame(maxWidth: .infinity)
 | 
			
		||||
                            .background(Color.blue)
 | 
			
		||||
                            .cornerRadius(8)
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                .disabled(viewModel.isLoading)
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            Spacer()
 | 
			
		||||
            
 | 
			
		||||
            // Кнопка регистрации
 | 
			
		||||
            Button(action: {
 | 
			
		||||
                isShowingRegistration = true
 | 
			
		||||
            }) {
 | 
			
		||||
                Text(NSLocalizedString("LoginView_button_register", comment: "Регистрация"))
 | 
			
		||||
                    .foregroundColor(.blue)
 | 
			
		||||
            }
 | 
			
		||||
            .padding(.top, 10)
 | 
			
		||||
            .sheet(isPresented: $isShowingRegistration) {
 | 
			
		||||
                RegistrationView()
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
        }
 | 
			
		||||
        .padding()
 | 
			
		||||
        .alert(isPresented: $viewModel.showError) {
 | 
			
		||||
            Alert(
 | 
			
		||||
                title: Text(NSLocalizedString("LoginView_error", comment: "")),
 | 
			
		||||
                message: Text(viewModel.errorMessage),
 | 
			
		||||
                dismissButton: .default(Text("ОК"))
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private func toggleTheme() {
 | 
			
		||||
        isDarkMode.toggle()
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private func openLanguageSettings() {
 | 
			
		||||
        guard let url = URL(string: UIApplication.openSettingsURLString) else { return }
 | 
			
		||||
        UIApplication.shared.open(url)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct LoginView_Previews: PreviewProvider {
 | 
			
		||||
    static var previews: some View {
 | 
			
		||||
        let viewModel = LoginViewModel()
 | 
			
		||||
        viewModel.isLoading = false // чтобы убрать спиннер
 | 
			
		||||
        return LoginView(viewModel: viewModel)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										38
									
								
								Shared/Views/MainView.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								Shared/Views/MainView.swift
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,38 @@
 | 
			
		||||
//
 | 
			
		||||
//  MainView.swift
 | 
			
		||||
//  VolnahubApp
 | 
			
		||||
//
 | 
			
		||||
//  Created by cheykrym on 09/06/2025.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
import SwiftUI
 | 
			
		||||
 | 
			
		||||
struct MainView: View {
 | 
			
		||||
    @ObservedObject var viewModel: LoginViewModel
 | 
			
		||||
    @State private var selectedTab: Int = 1 // Начинаем с Чатов (индекс 1)
 | 
			
		||||
    
 | 
			
		||||
    var body: some View {
 | 
			
		||||
        TabView(selection: $selectedTab) {
 | 
			
		||||
            ContactsTab()
 | 
			
		||||
                .tabItem {
 | 
			
		||||
                    Image(systemName: "person.2.fill")
 | 
			
		||||
                    Text("Контакты")
 | 
			
		||||
                }
 | 
			
		||||
                .tag(0)
 | 
			
		||||
            
 | 
			
		||||
            ChatsTab()
 | 
			
		||||
                .tabItem {
 | 
			
		||||
                    Image(systemName: "bubble.left.and.bubble.right.fill")
 | 
			
		||||
                    Text("Чаты")
 | 
			
		||||
                }
 | 
			
		||||
                .tag(1)
 | 
			
		||||
            
 | 
			
		||||
            SettingsTab()
 | 
			
		||||
                .tabItem {
 | 
			
		||||
                    Image(systemName: "gearshape.fill")
 | 
			
		||||
                    Text("Настройки")
 | 
			
		||||
                }
 | 
			
		||||
                .tag(2)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										134
									
								
								Shared/Views/RegistrationView.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										134
									
								
								Shared/Views/RegistrationView.swift
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,134 @@
 | 
			
		||||
//
 | 
			
		||||
//  RegistrationView.swift
 | 
			
		||||
//  VolnahubApp
 | 
			
		||||
//
 | 
			
		||||
//  Created by cheykrym on 09/06/2025.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
import SwiftUI
 | 
			
		||||
 | 
			
		||||
struct RegistrationView: View {
 | 
			
		||||
    @Environment(\.presentationMode) private var presentationMode
 | 
			
		||||
    
 | 
			
		||||
    @State private var fullName: String = ""
 | 
			
		||||
    @State private var username: String = ""
 | 
			
		||||
    @State private var password: String = ""
 | 
			
		||||
    @State private var confirmPassword: String = ""
 | 
			
		||||
    @State private var inviteCode: String = ""
 | 
			
		||||
    @AppStorage("isDarkMode") private var isDarkMode: Bool = true
 | 
			
		||||
 | 
			
		||||
    private var isUsernameValid: Bool {
 | 
			
		||||
        let pattern = "^[A-Za-z0-9_]{3,32}$"
 | 
			
		||||
        return username.range(of: pattern, options: .regularExpression) != nil
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private var isPasswordValid: Bool {
 | 
			
		||||
        password.count >= 6 && password.count <= 32
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private var isConfirmPasswordValid: Bool {
 | 
			
		||||
        confirmPassword == password && !confirmPassword.isEmpty
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private var isFormValid: Bool {
 | 
			
		||||
        !fullName.isEmpty && isUsernameValid && isPasswordValid && isConfirmPasswordValid
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    var body: some View {
 | 
			
		||||
        NavigationView {
 | 
			
		||||
            VStack {
 | 
			
		||||
 | 
			
		||||
                Text(NSLocalizedString("RegistrationView_title", comment: "Регистрация"))
 | 
			
		||||
                    .font(.largeTitle)
 | 
			
		||||
                    .bold()
 | 
			
		||||
 | 
			
		||||
//                Spacer()
 | 
			
		||||
 | 
			
		||||
                // Полное имя
 | 
			
		||||
                TextField(NSLocalizedString("RegistrationView_fullname", comment: "Полное имя"), text: $fullName)
 | 
			
		||||
                    .padding()
 | 
			
		||||
                    .background(Color(.secondarySystemBackground))
 | 
			
		||||
                    .cornerRadius(8)
 | 
			
		||||
                    .autocapitalization(.words)
 | 
			
		||||
                    .disableAutocorrection(true)
 | 
			
		||||
 | 
			
		||||
                // Логин
 | 
			
		||||
                TextField(NSLocalizedString("RegistrationView_login", comment: "Логин"), text: $username)
 | 
			
		||||
                    .padding()
 | 
			
		||||
                    .background(Color(.secondarySystemBackground))
 | 
			
		||||
                    .cornerRadius(8)
 | 
			
		||||
                    .autocapitalization(.none)
 | 
			
		||||
                    .disableAutocorrection(true)
 | 
			
		||||
 | 
			
		||||
                if !isUsernameValid && !username.isEmpty {
 | 
			
		||||
                    Text(NSLocalizedString("RegistrationView_error_username_invalid", comment: "Неверный логин"))
 | 
			
		||||
                        .foregroundColor(.red)
 | 
			
		||||
                        .font(.caption)
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Пароль
 | 
			
		||||
                SecureField(NSLocalizedString("RegistrationView_password", comment: "Пароль"), text: $password)
 | 
			
		||||
                    .padding()
 | 
			
		||||
                    .background(Color(.secondarySystemBackground))
 | 
			
		||||
                    .cornerRadius(8)
 | 
			
		||||
                    .autocapitalization(.none)
 | 
			
		||||
 | 
			
		||||
                if !isPasswordValid && !password.isEmpty {
 | 
			
		||||
                    Text(NSLocalizedString("RegistrationView_error_password_invalid", comment: "Пароль должен быть от 6 до 32 символов"))
 | 
			
		||||
                        .foregroundColor(.red)
 | 
			
		||||
                        .font(.caption)
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Подтверждение пароля
 | 
			
		||||
                SecureField(NSLocalizedString("RegistrationView_confirm_password", comment: "Подтверждение пароля"), text: $confirmPassword)
 | 
			
		||||
                    .padding()
 | 
			
		||||
                    .background(Color(.secondarySystemBackground))
 | 
			
		||||
                    .cornerRadius(8)
 | 
			
		||||
                    .autocapitalization(.none)
 | 
			
		||||
 | 
			
		||||
                if !isConfirmPasswordValid && !confirmPassword.isEmpty {
 | 
			
		||||
                    Text(NSLocalizedString("RegistrationView_error_confirm_password_invalid", comment: "Пароли не совпадают"))
 | 
			
		||||
                        .foregroundColor(.red)
 | 
			
		||||
                        .font(.caption)
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Инвайт-код
 | 
			
		||||
                TextField(NSLocalizedString("RegistrationView_invite", comment: "Инвайт-код"), text: $inviteCode)
 | 
			
		||||
                    .padding()
 | 
			
		||||
                    .background(Color(.secondarySystemBackground))
 | 
			
		||||
                    .cornerRadius(8)
 | 
			
		||||
                    .autocapitalization(.none)
 | 
			
		||||
                    .disableAutocorrection(true)
 | 
			
		||||
 | 
			
		||||
                // Кнопка регистрации
 | 
			
		||||
                Button(action: {
 | 
			
		||||
                    print("Регистрация отправлена")
 | 
			
		||||
                }) {
 | 
			
		||||
                    Text(NSLocalizedString("RegistrationView_button_register", comment: "Зарегистрироваться"))
 | 
			
		||||
                        .foregroundColor(.white)
 | 
			
		||||
                        .padding()
 | 
			
		||||
                        .frame(maxWidth: .infinity)
 | 
			
		||||
                        .background(isFormValid ? Color.blue : Color.gray.opacity(0.6))
 | 
			
		||||
                        .cornerRadius(8)
 | 
			
		||||
                }
 | 
			
		||||
                .disabled(!isFormValid)
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
            .padding()
 | 
			
		||||
            .navigationBarItems(trailing:
 | 
			
		||||
                Button(action: {
 | 
			
		||||
                    presentationMode.wrappedValue.dismiss()
 | 
			
		||||
                }) {
 | 
			
		||||
                    Text(NSLocalizedString("RegistrationView_close", comment: "Закрыть"))
 | 
			
		||||
                }
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct RegistrationView_Previews: PreviewProvider {
 | 
			
		||||
    static var previews: some View {
 | 
			
		||||
        RegistrationView()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										24
									
								
								Shared/Views/SettingsTab.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								Shared/Views/SettingsTab.swift
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,24 @@
 | 
			
		||||
//
 | 
			
		||||
//  SettingsTab.swift
 | 
			
		||||
//  VolnahubApp
 | 
			
		||||
//
 | 
			
		||||
//  Created by cheykrym on 09/06/2025.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
import SwiftUI
 | 
			
		||||
 | 
			
		||||
struct SettingsTab: View {
 | 
			
		||||
    var body: some View {
 | 
			
		||||
        NavigationView {
 | 
			
		||||
            VStack {
 | 
			
		||||
                Text("Настройки")
 | 
			
		||||
                    .font(.largeTitle)
 | 
			
		||||
                    .bold()
 | 
			
		||||
                    .padding()
 | 
			
		||||
                
 | 
			
		||||
                Spacer()
 | 
			
		||||
            }
 | 
			
		||||
            .navigationTitle("Настройки")
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										13
									
								
								Shared/Views/SplashScreenView.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								Shared/Views/SplashScreenView.swift
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,13 @@
 | 
			
		||||
import SwiftUI
 | 
			
		||||
 | 
			
		||||
struct SplashScreenView: View {
 | 
			
		||||
    var body: some View {
 | 
			
		||||
        VStack {
 | 
			
		||||
            ProgressView()
 | 
			
		||||
                .progressViewStyle(CircularProgressViewStyle())
 | 
			
		||||
                .scaleEffect(1.5)
 | 
			
		||||
            Text(NSLocalizedString("loading_placeholder", comment: ""))
 | 
			
		||||
                .padding(.top, 10)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										9
									
								
								Shared/config.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								Shared/config.swift
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,9 @@
 | 
			
		||||
import SwiftUI
 | 
			
		||||
 | 
			
		||||
struct AppConfig {
 | 
			
		||||
    static var DEBUG: Bool = false
 | 
			
		||||
    static let SERVICE = Bundle.main.bundleIdentifier ?? "default.service"
 | 
			
		||||
    static let PROTOCOL = "https"
 | 
			
		||||
    static let API_SERVER = "\(PROTOCOL)://api.volnahub.ru"
 | 
			
		||||
    static let SERVER_TIMEZONE = "GMT+3"
 | 
			
		||||
}
 | 
			
		||||
@ -7,11 +7,27 @@
 | 
			
		||||
 | 
			
		||||
import SwiftUI
 | 
			
		||||
 | 
			
		||||
//@AppStorage("isLoggedIn") var isLoggedIn: Bool = false
 | 
			
		||||
//@AppStorage("isDarkMode") private var isDarkMode: Bool = false
 | 
			
		||||
//@AppStorage("currentUser") var currentUser: String = ""
 | 
			
		||||
 | 
			
		||||
@main
 | 
			
		||||
struct volnahubApp: App {
 | 
			
		||||
    @AppStorage("isDarkMode") private var isDarkMode: Bool = true
 | 
			
		||||
    @StateObject private var viewModel = LoginViewModel()
 | 
			
		||||
 | 
			
		||||
    var body: some Scene {
 | 
			
		||||
        WindowGroup {
 | 
			
		||||
            ContentView()
 | 
			
		||||
            ZStack {
 | 
			
		||||
                Color(isDarkMode ? .black : .white) // ✅ Фон в зависимости от темы
 | 
			
		||||
 | 
			
		||||
                if viewModel.isLoggedIn {
 | 
			
		||||
                    MainView(viewModel: viewModel)
 | 
			
		||||
                } else {
 | 
			
		||||
                    LoginView(viewModel: viewModel)
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            .preferredColorScheme(isDarkMode ? .dark : .light)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -17,7 +17,7 @@
 | 
			
		||||
	<key>CFBundleShortVersionString</key>
 | 
			
		||||
	<string>1.0</string>
 | 
			
		||||
	<key>CFBundleVersion</key>
 | 
			
		||||
	<string>1</string>
 | 
			
		||||
	<string>$(CURRENT_PROJECT_VERSION)</string>
 | 
			
		||||
	<key>LSRequiresIPhoneOS</key>
 | 
			
		||||
	<true/>
 | 
			
		||||
	<key>UIApplicationSceneManifest</key>
 | 
			
		||||
 | 
			
		||||
@ -8,11 +8,23 @@
 | 
			
		||||
 | 
			
		||||
/* Begin PBXBuildFile section */
 | 
			
		||||
		1A79408D2DF77BC3002569DA /* volnahubApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A79407A2DF77BC2002569DA /* volnahubApp.swift */; };
 | 
			
		||||
		1A79408E2DF77BC3002569DA /* volnahubApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A79407A2DF77BC2002569DA /* volnahubApp.swift */; };
 | 
			
		||||
		1A79408F2DF77BC3002569DA /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A79407B2DF77BC2002569DA /* ContentView.swift */; };
 | 
			
		||||
		1A7940902DF77BC3002569DA /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A79407B2DF77BC2002569DA /* ContentView.swift */; };
 | 
			
		||||
		1A7940912DF77BC3002569DA /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1A79407C2DF77BC3002569DA /* Assets.xcassets */; };
 | 
			
		||||
		1A7940922DF77BC3002569DA /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1A79407C2DF77BC3002569DA /* Assets.xcassets */; };
 | 
			
		||||
		1A7940A22DF77DE9002569DA /* AuthService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A7940A12DF77DE9002569DA /* AuthService.swift */; };
 | 
			
		||||
		1A7940A62DF77DF5002569DA /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A7940A52DF77DF5002569DA /* User.swift */; };
 | 
			
		||||
		1A7940AA2DF77E05002569DA /* LoginViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A7940A92DF77E05002569DA /* LoginViewModel.swift */; };
 | 
			
		||||
		1A7940B02DF77E26002569DA /* LoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A7940AF2DF77E26002569DA /* LoginView.swift */; };
 | 
			
		||||
		1A7940B62DF77F21002569DA /* MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A7940B52DF77F21002569DA /* MainView.swift */; };
 | 
			
		||||
		1A7940C62DF7A98E002569DA /* ContactsTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A7940C52DF7A98E002569DA /* ContactsTab.swift */; };
 | 
			
		||||
		1A7940CA2DF7A99B002569DA /* ChatsTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A7940C92DF7A99B002569DA /* ChatsTab.swift */; };
 | 
			
		||||
		1A7940CE2DF7A9AA002569DA /* SettingsTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A7940CD2DF7A9AA002569DA /* SettingsTab.swift */; };
 | 
			
		||||
		1A7940DE2DF7B0D7002569DA /* config.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A7940DD2DF7B0D7002569DA /* config.swift */; };
 | 
			
		||||
		1A7940E22DF7B1C5002569DA /* KeychainService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A7940E12DF7B1C5002569DA /* KeychainService.swift */; };
 | 
			
		||||
		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 */; };
 | 
			
		||||
/* End PBXBuildFile section */
 | 
			
		||||
 | 
			
		||||
/* Begin PBXFileReference section */
 | 
			
		||||
@ -24,6 +36,20 @@
 | 
			
		||||
		1A7940892DF77BC3002569DA /* volnahub.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = volnahub.app; sourceTree = BUILT_PRODUCTS_DIR; };
 | 
			
		||||
		1A79408B2DF77BC3002569DA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
 | 
			
		||||
		1A79408C2DF77BC3002569DA /* macOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = macOS.entitlements; sourceTree = "<group>"; };
 | 
			
		||||
		1A7940A12DF77DE9002569DA /* AuthService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthService.swift; sourceTree = "<group>"; };
 | 
			
		||||
		1A7940A52DF77DF5002569DA /* User.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = "<group>"; };
 | 
			
		||||
		1A7940A92DF77E05002569DA /* LoginViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewModel.swift; sourceTree = "<group>"; };
 | 
			
		||||
		1A7940AF2DF77E26002569DA /* LoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginView.swift; sourceTree = "<group>"; };
 | 
			
		||||
		1A7940B52DF77F21002569DA /* MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainView.swift; sourceTree = "<group>"; };
 | 
			
		||||
		1A7940C52DF7A98E002569DA /* ContactsTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactsTab.swift; sourceTree = "<group>"; };
 | 
			
		||||
		1A7940C92DF7A99B002569DA /* ChatsTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatsTab.swift; sourceTree = "<group>"; };
 | 
			
		||||
		1A7940CD2DF7A9AA002569DA /* SettingsTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsTab.swift; sourceTree = "<group>"; };
 | 
			
		||||
		1A7940DD2DF7B0D7002569DA /* config.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = config.swift; sourceTree = "<group>"; };
 | 
			
		||||
		1A7940E12DF7B1C5002569DA /* KeychainService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainService.swift; sourceTree = "<group>"; };
 | 
			
		||||
		1A7940E62DF7B5E5002569DA /* SplashScreenView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashScreenView.swift; sourceTree = "<group>"; };
 | 
			
		||||
		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>"; };
 | 
			
		||||
/* End PBXFileReference section */
 | 
			
		||||
 | 
			
		||||
/* Begin PBXFrameworksBuildPhase section */
 | 
			
		||||
@ -57,9 +83,15 @@
 | 
			
		||||
		1A7940792DF77BC2002569DA /* Shared */ = {
 | 
			
		||||
			isa = PBXGroup;
 | 
			
		||||
			children = (
 | 
			
		||||
				1A7940FA2DF7B898002569DA /* Resources */,
 | 
			
		||||
				1A7940E52DF7B341002569DA /* Services */,
 | 
			
		||||
				1A7940A02DF77DCD002569DA /* Network */,
 | 
			
		||||
				1A79409F2DF77DC4002569DA /* Models */,
 | 
			
		||||
				1A79409E2DF77DBD002569DA /* ViewModels */,
 | 
			
		||||
				1A79409D2DF77DB5002569DA /* Views */,
 | 
			
		||||
				1A79407A2DF77BC2002569DA /* volnahubApp.swift */,
 | 
			
		||||
				1A79407B2DF77BC2002569DA /* ContentView.swift */,
 | 
			
		||||
				1A79407C2DF77BC3002569DA /* Assets.xcassets */,
 | 
			
		||||
				1A7940DD2DF7B0D7002569DA /* config.swift */,
 | 
			
		||||
			);
 | 
			
		||||
			path = Shared;
 | 
			
		||||
			sourceTree = "<group>";
 | 
			
		||||
@ -90,6 +122,61 @@
 | 
			
		||||
			path = macOS;
 | 
			
		||||
			sourceTree = "<group>";
 | 
			
		||||
		};
 | 
			
		||||
		1A79409D2DF77DB5002569DA /* Views */ = {
 | 
			
		||||
			isa = PBXGroup;
 | 
			
		||||
			children = (
 | 
			
		||||
				1A79407B2DF77BC2002569DA /* ContentView.swift */,
 | 
			
		||||
				1A7940AF2DF77E26002569DA /* LoginView.swift */,
 | 
			
		||||
				1A7940B52DF77F21002569DA /* MainView.swift */,
 | 
			
		||||
				1A7940C52DF7A98E002569DA /* ContactsTab.swift */,
 | 
			
		||||
				1A7940C92DF7A99B002569DA /* ChatsTab.swift */,
 | 
			
		||||
				1A7940CD2DF7A9AA002569DA /* SettingsTab.swift */,
 | 
			
		||||
				1A7940E62DF7B5E5002569DA /* SplashScreenView.swift */,
 | 
			
		||||
				1A79410B2DF7C81D002569DA /* RegistrationView.swift */,
 | 
			
		||||
			);
 | 
			
		||||
			path = Views;
 | 
			
		||||
			sourceTree = "<group>";
 | 
			
		||||
		};
 | 
			
		||||
		1A79409E2DF77DBD002569DA /* ViewModels */ = {
 | 
			
		||||
			isa = PBXGroup;
 | 
			
		||||
			children = (
 | 
			
		||||
				1A7940A92DF77E05002569DA /* LoginViewModel.swift */,
 | 
			
		||||
			);
 | 
			
		||||
			path = ViewModels;
 | 
			
		||||
			sourceTree = "<group>";
 | 
			
		||||
		};
 | 
			
		||||
		1A79409F2DF77DC4002569DA /* Models */ = {
 | 
			
		||||
			isa = PBXGroup;
 | 
			
		||||
			children = (
 | 
			
		||||
				1A7940A52DF77DF5002569DA /* User.swift */,
 | 
			
		||||
			);
 | 
			
		||||
			path = Models;
 | 
			
		||||
			sourceTree = "<group>";
 | 
			
		||||
		};
 | 
			
		||||
		1A7940A02DF77DCD002569DA /* Network */ = {
 | 
			
		||||
			isa = PBXGroup;
 | 
			
		||||
			children = (
 | 
			
		||||
				1A7940A12DF77DE9002569DA /* AuthService.swift */,
 | 
			
		||||
			);
 | 
			
		||||
			path = Network;
 | 
			
		||||
			sourceTree = "<group>";
 | 
			
		||||
		};
 | 
			
		||||
		1A7940E52DF7B341002569DA /* Services */ = {
 | 
			
		||||
			isa = PBXGroup;
 | 
			
		||||
			children = (
 | 
			
		||||
				1A7940E12DF7B1C5002569DA /* KeychainService.swift */,
 | 
			
		||||
			);
 | 
			
		||||
			path = Services;
 | 
			
		||||
			sourceTree = "<group>";
 | 
			
		||||
		};
 | 
			
		||||
		1A7940FA2DF7B898002569DA /* Resources */ = {
 | 
			
		||||
			isa = PBXGroup;
 | 
			
		||||
			children = (
 | 
			
		||||
				1A7940F22DF7B7A3002569DA /* Localizable.strings */,
 | 
			
		||||
			);
 | 
			
		||||
			path = Resources;
 | 
			
		||||
			sourceTree = "<group>";
 | 
			
		||||
		};
 | 
			
		||||
/* End PBXGroup section */
 | 
			
		||||
 | 
			
		||||
/* Begin PBXNativeTarget section */
 | 
			
		||||
@ -151,6 +238,7 @@
 | 
			
		||||
			knownRegions = (
 | 
			
		||||
				en,
 | 
			
		||||
				Base,
 | 
			
		||||
				ru,
 | 
			
		||||
			);
 | 
			
		||||
			mainGroup = 1A7940742DF77BC2002569DA;
 | 
			
		||||
			productRefGroup = 1A7940822DF77BC3002569DA /* Products */;
 | 
			
		||||
@ -169,6 +257,7 @@
 | 
			
		||||
			buildActionMask = 2147483647;
 | 
			
		||||
			files = (
 | 
			
		||||
				1A7940912DF77BC3002569DA /* Assets.xcassets in Resources */,
 | 
			
		||||
				1A7940F02DF7B7A3002569DA /* Localizable.strings in Resources */,
 | 
			
		||||
			);
 | 
			
		||||
			runOnlyForDeploymentPostprocessing = 0;
 | 
			
		||||
		};
 | 
			
		||||
@ -187,7 +276,19 @@
 | 
			
		||||
			isa = PBXSourcesBuildPhase;
 | 
			
		||||
			buildActionMask = 2147483647;
 | 
			
		||||
			files = (
 | 
			
		||||
				1A7940C62DF7A98E002569DA /* ContactsTab.swift in Sources */,
 | 
			
		||||
				1A7940E72DF7B5E5002569DA /* SplashScreenView.swift in Sources */,
 | 
			
		||||
				1A7940B02DF77E26002569DA /* LoginView.swift in Sources */,
 | 
			
		||||
				1A79410C2DF7C81D002569DA /* RegistrationView.swift in Sources */,
 | 
			
		||||
				1A7940CE2DF7A9AA002569DA /* SettingsTab.swift in Sources */,
 | 
			
		||||
				1A7940DE2DF7B0D7002569DA /* config.swift in Sources */,
 | 
			
		||||
				1A7940B62DF77F21002569DA /* MainView.swift in Sources */,
 | 
			
		||||
				1A7940E22DF7B1C5002569DA /* KeychainService.swift in Sources */,
 | 
			
		||||
				1A7940A62DF77DF5002569DA /* User.swift in Sources */,
 | 
			
		||||
				1A7940A22DF77DE9002569DA /* AuthService.swift in Sources */,
 | 
			
		||||
				1A79408F2DF77BC3002569DA /* ContentView.swift in Sources */,
 | 
			
		||||
				1A7940CA2DF7A99B002569DA /* ChatsTab.swift in Sources */,
 | 
			
		||||
				1A7940AA2DF77E05002569DA /* LoginViewModel.swift in Sources */,
 | 
			
		||||
				1A79408D2DF77BC3002569DA /* volnahubApp.swift in Sources */,
 | 
			
		||||
			);
 | 
			
		||||
			runOnlyForDeploymentPostprocessing = 0;
 | 
			
		||||
@ -197,17 +298,29 @@
 | 
			
		||||
			buildActionMask = 2147483647;
 | 
			
		||||
			files = (
 | 
			
		||||
				1A7940902DF77BC3002569DA /* ContentView.swift in Sources */,
 | 
			
		||||
				1A79408E2DF77BC3002569DA /* volnahubApp.swift in Sources */,
 | 
			
		||||
			);
 | 
			
		||||
			runOnlyForDeploymentPostprocessing = 0;
 | 
			
		||||
		};
 | 
			
		||||
/* End PBXSourcesBuildPhase section */
 | 
			
		||||
 | 
			
		||||
/* Begin PBXVariantGroup section */
 | 
			
		||||
		1A7940F22DF7B7A3002569DA /* Localizable.strings */ = {
 | 
			
		||||
			isa = PBXVariantGroup;
 | 
			
		||||
			children = (
 | 
			
		||||
				1A7940F12DF7B7A3002569DA /* en */,
 | 
			
		||||
				1A7940F72DF7B7EC002569DA /* ru */,
 | 
			
		||||
			);
 | 
			
		||||
			name = Localizable.strings;
 | 
			
		||||
			sourceTree = "<group>";
 | 
			
		||||
		};
 | 
			
		||||
/* End PBXVariantGroup section */
 | 
			
		||||
 | 
			
		||||
/* Begin XCBuildConfiguration section */
 | 
			
		||||
		1A7940932DF77BC3002569DA /* Debug */ = {
 | 
			
		||||
			isa = XCBuildConfiguration;
 | 
			
		||||
			buildSettings = {
 | 
			
		||||
				ALWAYS_SEARCH_USER_PATHS = NO;
 | 
			
		||||
				CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
 | 
			
		||||
				CLANG_ANALYZER_NONNULL = YES;
 | 
			
		||||
				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
 | 
			
		||||
				CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
 | 
			
		||||
@ -267,6 +380,7 @@
 | 
			
		||||
			isa = XCBuildConfiguration;
 | 
			
		||||
			buildSettings = {
 | 
			
		||||
				ALWAYS_SEARCH_USER_PATHS = NO;
 | 
			
		||||
				CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
 | 
			
		||||
				CLANG_ANALYZER_NONNULL = YES;
 | 
			
		||||
				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
 | 
			
		||||
				CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
 | 
			
		||||
@ -321,6 +435,7 @@
 | 
			
		||||
				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 | 
			
		||||
				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
 | 
			
		||||
				CODE_SIGN_STYLE = Automatic;
 | 
			
		||||
				CURRENT_PROJECT_VERSION = 1337;
 | 
			
		||||
				ENABLE_PREVIEWS = YES;
 | 
			
		||||
				INFOPLIST_FILE = iOS/Info.plist;
 | 
			
		||||
				IPHONEOS_DEPLOYMENT_TARGET = 14.0;
 | 
			
		||||
@ -342,6 +457,7 @@
 | 
			
		||||
				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 | 
			
		||||
				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
 | 
			
		||||
				CODE_SIGN_STYLE = Automatic;
 | 
			
		||||
				CURRENT_PROJECT_VERSION = 1337;
 | 
			
		||||
				ENABLE_PREVIEWS = YES;
 | 
			
		||||
				INFOPLIST_FILE = iOS/Info.plist;
 | 
			
		||||
				IPHONEOS_DEPLOYMENT_TARGET = 14.0;
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user