diff --git a/Shared/Models/User.swift b/Shared/Models/User.swift new file mode 100644 index 0000000..c8dc186 --- /dev/null +++ b/Shared/Models/User.swift @@ -0,0 +1,8 @@ +// +// User.swift +// volnahub (iOS) +// +// Created by cheykrym on 09/06/2025. +// + +import Foundation diff --git a/Shared/Network/AuthService.swift b/Shared/Network/AuthService.swift new file mode 100644 index 0000000..92e140a --- /dev/null +++ b/Shared/Network/AuthService.swift @@ -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, "Неверные учетные данные.") + } + } + } +} diff --git a/Shared/Resources/en.lproj/Localizable.strings b/Shared/Resources/en.lproj/Localizable.strings new file mode 100644 index 0000000..b9cd02e --- /dev/null +++ b/Shared/Resources/en.lproj/Localizable.strings @@ -0,0 +1,7 @@ +/* + Localizable.strings + volnahub + + Created by cheykrym on 10/06/2025. + +*/ diff --git a/Shared/Resources/ru.lproj/Localizable.strings b/Shared/Resources/ru.lproj/Localizable.strings new file mode 100644 index 0000000..70d9123 --- /dev/null +++ b/Shared/Resources/ru.lproj/Localizable.strings @@ -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" = "Регистрация"; diff --git a/Shared/Services/KeychainService.swift b/Shared/Services/KeychainService.swift new file mode 100644 index 0000000..14f7503 --- /dev/null +++ b/Shared/Services/KeychainService.swift @@ -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() + 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)") + } + } +} diff --git a/Shared/ViewModels/LoginViewModel.swift b/Shared/ViewModels/LoginViewModel.swift new file mode 100644 index 0000000..8348f96 --- /dev/null +++ b/Shared/ViewModels/LoginViewModel.swift @@ -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 + } + +} diff --git a/Shared/Views/ChatsTab.swift b/Shared/Views/ChatsTab.swift new file mode 100644 index 0000000..804b05a --- /dev/null +++ b/Shared/Views/ChatsTab.swift @@ -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("Чаты") + } + } +} diff --git a/Shared/Views/ContactsTab.swift b/Shared/Views/ContactsTab.swift new file mode 100644 index 0000000..6a896ab --- /dev/null +++ b/Shared/Views/ContactsTab.swift @@ -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("Контакты") + } + } +} diff --git a/Shared/ContentView.swift b/Shared/Views/ContentView.swift similarity index 100% rename from Shared/ContentView.swift rename to Shared/Views/ContentView.swift diff --git a/Shared/Views/LoginView.swift b/Shared/Views/LoginView.swift new file mode 100644 index 0000000..ba1ff8b --- /dev/null +++ b/Shared/Views/LoginView.swift @@ -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) + } +} diff --git a/Shared/Views/MainView.swift b/Shared/Views/MainView.swift new file mode 100644 index 0000000..d285059 --- /dev/null +++ b/Shared/Views/MainView.swift @@ -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) + } + } +} diff --git a/Shared/Views/RegistrationView.swift b/Shared/Views/RegistrationView.swift new file mode 100644 index 0000000..ba60051 --- /dev/null +++ b/Shared/Views/RegistrationView.swift @@ -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() + } +} diff --git a/Shared/Views/SettingsTab.swift b/Shared/Views/SettingsTab.swift new file mode 100644 index 0000000..e014cc8 --- /dev/null +++ b/Shared/Views/SettingsTab.swift @@ -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("Настройки") + } + } +} diff --git a/Shared/Views/SplashScreenView.swift b/Shared/Views/SplashScreenView.swift new file mode 100644 index 0000000..658316a --- /dev/null +++ b/Shared/Views/SplashScreenView.swift @@ -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) + } + } +} diff --git a/Shared/config.swift b/Shared/config.swift new file mode 100644 index 0000000..a098dfb --- /dev/null +++ b/Shared/config.swift @@ -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" +} diff --git a/Shared/volnahubApp.swift b/Shared/volnahubApp.swift index a2ad3a4..5218d59 100644 --- a/Shared/volnahubApp.swift +++ b/Shared/volnahubApp.swift @@ -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) } } } diff --git a/iOS/Info.plist b/iOS/Info.plist index efc211a..8cec9d0 100644 --- a/iOS/Info.plist +++ b/iOS/Info.plist @@ -17,7 +17,7 @@ CFBundleShortVersionString 1.0 CFBundleVersion - 1 + $(CURRENT_PROJECT_VERSION) LSRequiresIPhoneOS UIApplicationSceneManifest diff --git a/volnahub.xcodeproj/project.pbxproj b/volnahub.xcodeproj/project.pbxproj index 0927857..5eefaad 100644 --- a/volnahub.xcodeproj/project.pbxproj +++ b/volnahub.xcodeproj/project.pbxproj @@ -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 = ""; }; 1A79408C2DF77BC3002569DA /* macOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = macOS.entitlements; sourceTree = ""; }; + 1A7940A12DF77DE9002569DA /* AuthService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthService.swift; sourceTree = ""; }; + 1A7940A52DF77DF5002569DA /* User.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = ""; }; + 1A7940A92DF77E05002569DA /* LoginViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewModel.swift; sourceTree = ""; }; + 1A7940AF2DF77E26002569DA /* LoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginView.swift; sourceTree = ""; }; + 1A7940B52DF77F21002569DA /* MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainView.swift; sourceTree = ""; }; + 1A7940C52DF7A98E002569DA /* ContactsTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactsTab.swift; sourceTree = ""; }; + 1A7940C92DF7A99B002569DA /* ChatsTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatsTab.swift; sourceTree = ""; }; + 1A7940CD2DF7A9AA002569DA /* SettingsTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsTab.swift; sourceTree = ""; }; + 1A7940DD2DF7B0D7002569DA /* config.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = config.swift; sourceTree = ""; }; + 1A7940E12DF7B1C5002569DA /* KeychainService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainService.swift; sourceTree = ""; }; + 1A7940E62DF7B5E5002569DA /* SplashScreenView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashScreenView.swift; sourceTree = ""; }; + 1A7940F12DF7B7A3002569DA /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; + 1A7940F72DF7B7EC002569DA /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = ""; }; + 1A79410B2DF7C81D002569DA /* RegistrationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegistrationView.swift; sourceTree = ""; }; /* 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 = ""; @@ -90,6 +122,61 @@ path = macOS; sourceTree = ""; }; + 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 = ""; + }; + 1A79409E2DF77DBD002569DA /* ViewModels */ = { + isa = PBXGroup; + children = ( + 1A7940A92DF77E05002569DA /* LoginViewModel.swift */, + ); + path = ViewModels; + sourceTree = ""; + }; + 1A79409F2DF77DC4002569DA /* Models */ = { + isa = PBXGroup; + children = ( + 1A7940A52DF77DF5002569DA /* User.swift */, + ); + path = Models; + sourceTree = ""; + }; + 1A7940A02DF77DCD002569DA /* Network */ = { + isa = PBXGroup; + children = ( + 1A7940A12DF77DE9002569DA /* AuthService.swift */, + ); + path = Network; + sourceTree = ""; + }; + 1A7940E52DF7B341002569DA /* Services */ = { + isa = PBXGroup; + children = ( + 1A7940E12DF7B1C5002569DA /* KeychainService.swift */, + ); + path = Services; + sourceTree = ""; + }; + 1A7940FA2DF7B898002569DA /* Resources */ = { + isa = PBXGroup; + children = ( + 1A7940F22DF7B7A3002569DA /* Localizable.strings */, + ); + path = Resources; + sourceTree = ""; + }; /* 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 = ""; + }; +/* 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;