diff --git a/Shared/Network/AuthService.swift b/Shared/Network/AuthService.swift
index 92e140a..9d0cdd9 100644
--- a/Shared/Network/AuthService.swift
+++ b/Shared/Network/AuthService.swift
@@ -8,22 +8,287 @@
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" {
+ func autoLogin(completion: @escaping (Bool, String?) -> Void) {
+ // 1️⃣ Проверяем наличие текущего пользователя
+ if let currentUser = UserDefaults.standard.string(forKey: "currentUser"),
+ let accessToken = KeychainService.shared.get(forKey: "access_token", service: currentUser),
+ let refreshToken = KeychainService.shared.get(forKey: "refresh_token", service: currentUser) {
+ if AppConfig.DEBUG{ print("AutoLogin: найден текущий пользователь — \(currentUser)")}
+ completion(true, nil)
+ return
+ }
+
+ // 2️⃣ Текущий пользователь не найден или токены отсутствуют
+ if AppConfig.DEBUG{ print("AutoLogin: текущий пользователь не найден или токены отсутствуют. Пробуем найти другого пользователя...")}
+
+ let allUsers = KeychainService.shared.getAllServices()
+
+ for user in allUsers {
+ let hasAccessToken = KeychainService.shared.get(forKey: "access_token", service: user) != nil
+ let hasRefreshToken = KeychainService.shared.get(forKey: "refresh_token", service: user) != nil
+
+ if hasAccessToken && hasRefreshToken {
+ // Нашли пользователя с токенами — назначаем как currentUser
+ UserDefaults.standard.set(user, forKey: "currentUser")
+ if AppConfig.DEBUG{ print("AutoLogin: переключились на пользователя \(user)")}
completion(true, nil)
- } else {
- completion(false, "Неверные учетные данные.")
+ return
}
}
+
+ // 3️⃣ Если никто не найден
+// completion(false, "Не найден авторизованный пользователь. Пожалуйста, войдите снова.")
+ completion(false, nil)
+ }
+
+
+ func login(username: String, password: String, completion: @escaping (Bool, String?) -> Void) {
+ let url = URL(string: "\(AppConfig.API_SERVER)/auth/login")!
+ var request = URLRequest(url: url)
+ request.httpMethod = "POST"
+ request.setValue("application/json", forHTTPHeaderField: "Content-Type")
+ request.setValue("\(AppConfig.USER_AGENT)", forHTTPHeaderField: "User-Agent")
+
+ let payload: [String: String] = [
+ "login": username,
+ "password": password
+ ]
+
+ do {
+ let jsonData = try JSONEncoder().encode(payload)
+ request.httpBody = jsonData
+ } catch {
+ DispatchQueue.main.async {
+ completion(false, NSLocalizedString("AuthService_error_serialization", comment: ""))
+ }
+ return
+ }
+
+ let task = URLSession.shared.dataTask(with: request) { data, response, error in
+ DispatchQueue.main.async {
+ if let error = error {
+ let errorMessage = String(format: NSLocalizedString("AuthService_error_network", comment: ""), error.localizedDescription)
+ completion(false, errorMessage)
+ return
+ }
+
+ guard let httpResponse = response as? HTTPURLResponse else {
+ completion(false, NSLocalizedString("AuthService_error_invalid_response", comment: ""))
+ return
+ }
+
+ guard (200...299).contains(httpResponse.statusCode) else {
+ if httpResponse.statusCode == 401{
+ completion(false, NSLocalizedString("AuthService_error_invalid_credentials", comment: ""))
+ } else if httpResponse.statusCode == 502{
+ completion(false, NSLocalizedString("AuthService_error_server_unavailable", comment: ""))
+ } else if httpResponse.statusCode == 429 {
+ completion(false, NSLocalizedString("AuthService_error_too_many_requests", comment: ""))
+ } else {
+ let errorMessage = String(format: NSLocalizedString("AuthService_error_server_error", comment: ""), "\(httpResponse.statusCode)")
+ completion(false, errorMessage)
+ }
+ return
+ }
+
+ guard let data = data else {
+ completion(false, NSLocalizedString("AuthService_error_empty_response", comment: ""))
+ return
+ }
+
+ do {
+ let decoder = JSONDecoder()
+ let loginResponse = try decoder.decode(LoginResponse.self, from: data)
+
+ // Сохраняем токены в Keychain
+ KeychainService.shared.save(loginResponse.access_token, forKey: "access_token", service: username)
+ KeychainService.shared.save(loginResponse.refresh_token, forKey: "refresh_token", service: username)
+ UserDefaults.standard.set(username, forKey: "currentUser")
+
+ completion(true, nil)
+ } catch {
+ completion(false, NSLocalizedString("AuthService_error_parsing_response", comment: ""))
+ }
+ }
+ }
+ task.resume()
+ }
+
+ func register(username: String, password: String, invite: String?, completion: @escaping (Bool, String?) -> Void) {
+ let url = URL(string: "\(AppConfig.API_SERVER)/auth/register")!
+ var request = URLRequest(url: url)
+ request.httpMethod = "POST"
+ request.setValue("application/json", forHTTPHeaderField: "Content-Type")
+ request.setValue(AppConfig.USER_AGENT, forHTTPHeaderField: "User-Agent")
+
+ let payload: [String: Any] = [
+ "login": username,
+ "password": password,
+ "invite": invite ?? NSNull()
+ ]
+
+ do {
+ let jsonData = try JSONSerialization.data(withJSONObject: payload)
+ request.httpBody = jsonData
+ } catch {
+ DispatchQueue.main.async {
+ completion(false, NSLocalizedString("AuthService_error_serialization", comment: ""))
+ }
+ return
+ }
+
+ let task = URLSession.shared.dataTask(with: request) { data, response, error in
+ DispatchQueue.main.async {
+ if let error = error {
+ let errorMessage = String(format: NSLocalizedString("AuthService_error_network", comment: ""), error.localizedDescription)
+ completion(false, errorMessage)
+ return
+ }
+
+ guard let httpResponse = response as? HTTPURLResponse else {
+ completion(false, NSLocalizedString("AuthService_error_invalid_response", comment: ""))
+ return
+ }
+
+ guard let data = data else {
+ completion(false, NSLocalizedString("AuthService_error_empty_response", comment: ""))
+ return
+ }
+
+ let decoder = JSONDecoder()
+
+ if (200...299).contains(httpResponse.statusCode) {
+ do {
+ let _ = try decoder.decode(RegisterResponse.self, from: data)
+ if AppConfig.DEBUG{ print("Регистрация успешна. Пытаемся сразу войти...")}
+
+ // Сразу логинимся
+ self.login(username: username, password: password) { loginSuccess, loginMessage in
+ if loginSuccess {
+ completion(true, "Регистрация и вход выполнены успешно.")
+ } else {
+ // Регистрация успешна, но логин не удался — покажем сообщение
+ completion(false, loginMessage ?? NSLocalizedString("AuthService_login_success_but_failed", comment: ""))
+ }
+ }
+ } catch {
+ completion(false, NSLocalizedString("AuthService_error_parsing_response", comment: ""))
+ }
+ } else {
+ // Ошибка сервера — пробуем распарсить message
+ if let errorResponseMessage = try? decoder.decode(ErrorResponseMessage.self, from: data),
+ let message = errorResponseMessage.message {
+
+ if let jsonString = String(data: data, encoding: .utf8) {
+ if AppConfig.DEBUG{ print("Raw JSON:", jsonString)}
+ }
+ if AppConfig.DEBUG{ print("message:", message)}
+
+ if httpResponse.statusCode == 400 {
+ if message.contains("Invalid invitation code") {
+ completion(false, NSLocalizedString("AuthService_error_invalid_invitation_code", comment: ""))
+ } else if message.contains("This invitation is not active") {
+ completion(false, NSLocalizedString("AuthService_error_invitation_not_active", comment: ""))
+ } else if message.contains("This invitation has reached its usage limit") {
+ completion(false, NSLocalizedString("AuthService_error_invitation_usage_limit", comment: ""))
+ } else if message.contains("This invitation has expired") {
+ completion(false, NSLocalizedString("AuthService_error_invitation_expired", comment: ""))
+ } else if message.contains("Login already registered") {
+ completion(false, NSLocalizedString("AuthService_error_login_already_registered", comment: ""))
+ } else {
+ completion(false, message)
+ }
+ } else if httpResponse.statusCode == 403 {
+ if message.contains("Registration is currently disabled") {
+ completion(false, NSLocalizedString("AuthService_error_registration_disabled", comment: ""))
+ } else {
+ completion(false, message)
+ }
+ } else if httpResponse.statusCode == 429 {
+ completion(false, NSLocalizedString("AuthService_error_too_many_requests", comment: ""))
+ } else if httpResponse.statusCode == 502{
+ completion(false, NSLocalizedString("AuthService_error_server_unavailable", comment: ""))
+ } else {
+ let errorMessage = String(format: NSLocalizedString("AuthService_error_server_error", comment: ""), "\(httpResponse.statusCode)")
+ completion(false, errorMessage)
+ }
+ } else {
+ // Не удалось распарсить JSON — fallback
+ if httpResponse.statusCode == 400 {
+ completion(false, NSLocalizedString("AuthService_error_invalid_request", comment: ""))
+ } else if httpResponse.statusCode == 403 {
+ completion(false, NSLocalizedString("AuthService_error_registration_forbidden", comment: ""))
+ } else if httpResponse.statusCode == 429 {
+ completion(false, NSLocalizedString("AuthService_error_too_many_requests", comment: ""))
+ } else if httpResponse.statusCode == 502{
+ completion(false, NSLocalizedString("AuthService_error_server_unavailable", comment: ""))
+ } else {
+ let errorMessage = String(format: NSLocalizedString("AuthService_error_server_error", comment: ""), "\(httpResponse.statusCode)")
+ completion(false, errorMessage)
+ }
+ }
+ }
+ }
+ }
+ task.resume()
+ }
+
+
+
+
+ func logoutCurrentUser(completion: @escaping (Bool, String?) -> Void) {
+ guard let currentUser = UserDefaults.standard.string(forKey: "currentUser") else {
+ completion(false, "Не найден текущий пользователь.")
+ return
+ }
+
+ // Удаляем токены текущего пользователя
+ KeychainService.shared.delete(forKey: "access_token", service: currentUser)
+ KeychainService.shared.delete(forKey: "refresh_token", service: currentUser)
+
+ // Сбрасываем текущего пользователя
+ UserDefaults.standard.removeObject(forKey: "currentUser")
+
+ // Пробуем переключиться на другого пользователя
+ let allUsers = KeychainService.shared.getAllServices()
+ for user in allUsers {
+ let hasAccessToken = KeychainService.shared.get(forKey: "access_token", service: user) != nil
+ let hasRefreshToken = KeychainService.shared.get(forKey: "refresh_token", service: user) != nil
+
+ if hasAccessToken && hasRefreshToken {
+ UserDefaults.standard.set(user, forKey: "currentUser")
+ if AppConfig.DEBUG{ print("Logout: переключились на пользователя \(user)")}
+ completion(true, nil)
+ return
+ }
+ }
+
+ // Если пользователей больше нет
+// completion(false, "Нет доступных пользователей. Пожалуйста, войдите снова.")
+ completion(false, nil)
}
}
+
+struct LoginResponse: Decodable {
+ let status: String
+ let access_token: String
+ let refresh_token: String
+ let token_type: String
+}
+
+struct TokenRefreshResponse: Decodable {
+ let status: String
+ let access_token: String
+ let token_type: String
+}
+
+struct RegisterResponse: Decodable {
+ let status: String
+ let message: String
+}
+
+struct ErrorResponseMessage: Decodable {
+ let status: String?
+ let message: String?
+}
diff --git a/Shared/Network/refreshtokenex.swift b/Shared/Network/refreshtokenex.swift
new file mode 100644
index 0000000..7852ec8
--- /dev/null
+++ b/Shared/Network/refreshtokenex.swift
@@ -0,0 +1,89 @@
+////
+//// refreshtokenex.swift
+//// volnahub (iOS)
+////
+//// Created by cheykrym on 11/06/2025.
+////
+//
+//import Foundation
+//
+//func autoLogin(completion: @escaping (Bool, String?) -> Void) {
+// guard let username = UserDefaults.standard.string(forKey: "currentUser") else {
+// completion(false, "Не найден текущий пользователь")
+// return
+// }
+//
+// guard let accessToken = KeychainService.shared.get(forKey: "access_token", service: username),
+// let refreshToken = KeychainService.shared.get(forKey: "refresh_token", service: username) else {
+// completion(false, "Токены отсутствуют. Пожалуйста, войдите снова.")
+// return
+// }
+//
+//// let url = URL(string: "\(AppConfig.API_SERVER)/auth/refresh")!
+//// var request = URLRequest(url: url)
+//// request.httpMethod = "POST"
+//// request.setValue("application/json", forHTTPHeaderField: "Content-Type")
+////// request.setValue("VolnahubApp", forHTTPHeaderField: "User-Agent")
+//// request.setValue("VolnahubApp", forHTTPHeaderField: "\(AppConfig.USER_AGENT)")
+////
+//// let payload: [String: String] = [
+//// "access_token": accessToken,
+//// "refresh_token": refreshToken
+//// ]
+////
+//// do {
+//// let jsonData = try JSONEncoder().encode(payload)
+//// request.httpBody = jsonData
+//// } catch {
+//// DispatchQueue.main.async {
+//// completion(false, "Не удалось подготовить запрос.")
+//// }
+//// return
+//// }
+////
+//// let task = URLSession.shared.dataTask(with: request) { data, response, error in
+//// DispatchQueue.main.async {
+//// if let error = error {
+//// print("AutoLogin error: \(error.localizedDescription)")
+//// completion(false, "Ошибка сети: \(error.localizedDescription)")
+//// return
+//// }
+////
+//// guard let httpResponse = response as? HTTPURLResponse else {
+//// completion(false, "Некорректный ответ от сервера.")
+//// return
+//// }
+////
+//// guard (200...299).contains(httpResponse.statusCode) else {
+//// if httpResponse.statusCode == 401 {
+//// print("деавторизация") //TODO
+//// completion(false, "Сессия недействительна. Пожалуйста, войдите снова.")
+//// } else if httpResponse.statusCode == 502 {
+//// completion(false, "Сервер не отвечает. Попробуйте позже.")
+//// } else {
+//// completion(false, "Ошибка сервера: \(httpResponse.statusCode)")
+//// }
+//// return
+//// }
+////
+//// guard let data = data else {
+//// completion(false, "Пустой ответ от сервера.")
+//// return
+//// }
+////
+//// do {
+//// let decoder = JSONDecoder()
+//// let refreshResponse = try decoder.decode(TokenRefreshResponse.self, from: data)
+////
+//// KeychainService.shared.save(refreshResponse.access_token, forKey: "access_token", service: username)
+////
+//// print("AutoLogin: токен обновлён.")
+//// completion(true, nil)
+//// } catch {
+//// print("AutoLogin decode error: \(error.localizedDescription)")
+//// completion(false, "Ошибка обработки ответа сервера.")
+//// }
+//// }
+//// }
+//// task.resume()
+//}
diff --git a/Shared/Resources/en.lproj/Localizable.strings b/Shared/Resources/en.lproj/Localizable.strings
index b9cd02e..b0a37e3 100644
--- a/Shared/Resources/en.lproj/Localizable.strings
+++ b/Shared/Resources/en.lproj/Localizable.strings
@@ -1,7 +1,54 @@
-/*
+/*
Localizable.strings
volnahub
Created by cheykrym on 10/06/2025.
-
*/
+
+/* General */
+"ok" = "OK";
+"loading_placeholder" = "Loading...";
+
+/* LoginView */
+"LoginView_change_language" = "Language";
+"LoginView_login" = "Login";
+"LoginView_password" = "Password";
+"LoginView_button_login" = "Log in";
+"LoginView_error" = "Login error";
+"LoginView_button_register" = "Register";
+"LoginView_error_username_invalid" = "Username must be 3 to 32 characters (letters, digits, or _)";
+"LoginView_error_password_invalid" = "Password must be 6 to 32 characters long";
+
+/* RegistrationView */
+"RegistrationView_title" = "Registration";
+"RegistrationView_fullname" = "Full name";
+"RegistrationView_login" = "Login";
+"RegistrationView_error_username_invalid" = "Username must be 3 to 32 characters (letters, digits, or _)";
+"RegistrationView_password" = "Password";
+"RegistrationView_error_password_invalid" = "Password must be 6 to 32 characters long";
+"RegistrationView_confirm_password" = "Confirm password";
+"RegistrationView_error_confirm_password_invalid" = "Passwords do not match";
+"RegistrationView_invite" = "Invite code (optional)";
+"RegistrationView_button_register" = "Register";
+"RegistrationView_close" = "Close";
+"RegistrationView_error" = "Registration error";
+
+/* AuthService */
+"AuthService_error_invalid_invitation_code" = "Invalid invitation code.";
+"AuthService_error_invitation_not_active" = "The invitation is not active.";
+"AuthService_error_invitation_usage_limit" = "The invitation has reached its usage limit.";
+"AuthService_error_invitation_expired" = "The invitation has expired.";
+"AuthService_error_login_already_registered" = "This login is already registered.";
+"AuthService_error_registration_disabled" = "Registration is temporarily unavailable.";
+"AuthService_error_server_unavailable" = "Server is unavailable. Please try again later.";
+"AuthService_error_too_many_requests" = "Too many requests.";
+"AuthService_error_invalid_request" = "Invalid request (400).";
+"AuthService_error_registration_forbidden" = "Registration is forbidden.";
+"AuthService_error_server_error" = "Server error: %@";
+"AuthService_error_network" = "Network error: %@";
+"AuthService_error_invalid_response" = "Invalid server response.";
+"AuthService_error_invalid_credentials" = "Invalid username or password.";
+"AuthService_error_empty_response" = "Empty server response.";
+"AuthService_error_parsing_response" = "Failed to parse server response.";
+"AuthService_error_serialization" = "Failed to serialize request data.";
+"AuthService_login_success_but_failed" = "Registration succeeded, but login failed.";
diff --git a/Shared/Resources/ru.lproj/Localizable.strings b/Shared/Resources/ru.lproj/Localizable.strings
index 70d9123..9c0803d 100644
--- a/Shared/Resources/ru.lproj/Localizable.strings
+++ b/Shared/Resources/ru.lproj/Localizable.strings
@@ -5,10 +5,54 @@
Created by cheykrym on 10/06/2025.
*/
+"ok" = "OK";
"loading_placeholder" = "Загрузка...";
+
+/* LoginView */
"LoginView_change_language" = "Язык";
-"LoginView_title" = "Вход";
"LoginView_login" = "Логин";
"LoginView_password" = "Пароль";
"LoginView_button_login" = "Войти";
+"LoginView_error" = "Ошибка авторизации";
"LoginView_button_register" = "Регистрация";
+"LoginView_error_username_invalid" = "Логин должен быть от 3 до 32 символов (английские буквы, цифры, _)";
+"LoginView_error_password_invalid" = "Пароль должен быть от 6 до 32 символов";
+
+/* RegistrationView */
+"RegistrationView_title" = "Регистрация";
+"RegistrationView_fullname" = "Имя";
+"RegistrationView_login" = "Логин";
+"RegistrationView_error_username_invalid" = "Логин должен быть от 3 до 32 символов (английские буквы, цифры, _)";
+"RegistrationView_password" = "Пароль";
+"RegistrationView_error_password_invalid" = "Пароль должен быть от 6 до 32 символов";
+"RegistrationView_confirm_password" = "Подтверждение пароля";
+"RegistrationView_error_confirm_password_invalid" = "Пароли не совпадают";
+"RegistrationView_invite" = "Инвайт-код (необязательно)";
+"RegistrationView_button_register" = "Зарегистрироваться";
+"RegistrationView_close" = "Закрыть";
+"RegistrationView_error" = "Ошибка регистрация";
+
+/* AuthService */
+"AuthService_error_invalid_invitation_code" = "Неверный код приглашения.";
+"AuthService_error_invitation_not_active" = "Приглашение не активно.";
+"AuthService_error_invitation_usage_limit" = "Приглашение достигло лимита использования.";
+"AuthService_error_invitation_expired" = "Приглашение истекло.";
+"AuthService_error_login_already_registered" = "Логин уже занят.";
+"AuthService_error_registration_disabled" = "Регистрация временно недоступна.";
+"AuthService_error_server_unavailable" = "Сервер не отвечает. Попробуйте позже.";
+"AuthService_error_too_many_requests" = "Слишком много запросов.";
+"AuthService_error_invalid_request" = "Неверный запрос (400).";
+"AuthService_error_registration_forbidden" = "Регистрация запрещена.";
+"AuthService_error_server_error" = "Ошибка сервера: %@";
+"AuthService_error_network" = "Ошибка сети: %@";
+"AuthService_error_invalid_response" = "Некорректный ответ от сервера.";
+"AuthService_error_invalid_credentials" = "Неверный логин или пароль.";
+"AuthService_error_empty_response" = "Пустой ответ от сервера.";
+"AuthService_error_parsing_response" = "Не удалось обработать ответ сервера.";
+"AuthService_error_serialization" = "Не удалось сериализовать данные запроса.";
+"AuthService_login_success_but_failed" = "Регистрация выполнена, но вход не удался.";
+
+/* MainView */
+"MainView_contacts" = "Контакты";
+"MainView_chats" = "Чаты";
+"MainView_settings" = "Настройки";
diff --git a/Shared/ViewModels/LoginViewModel.swift b/Shared/ViewModels/LoginViewModel.swift
index 8348f96..ad32f91 100644
--- a/Shared/ViewModels/LoginViewModel.swift
+++ b/Shared/ViewModels/LoginViewModel.swift
@@ -11,28 +11,39 @@ import Combine
class LoginViewModel: ObservableObject {
@Published var username: String = ""
@Published var password: String = ""
- @Published var isLoading: Bool = true // сразу true, чтобы вызывался автологин
+ @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() {
+ // Если username сохранён, подставим его сразу
+ if let savedUsername = UserDefaults.standard.string(forKey: "currentUser") {
+ username = savedUsername
+ }
+
+ // Запускаем автологин
autoLogin()
}
-
+
func autoLogin() {
- authService.autoLogin { [weak self] success in
+ authService.autoLogin { [weak self] success, error in
DispatchQueue.main.async {
self?.isLoading = false
if success {
self?.isLoggedIn = true
+ } else {
+ self?.isLoggedIn = false
+ self?.errorMessage = error ?? "Произошла ошибка."
+ self?.showError = false
}
}
}
}
-
+
+
func login() {
isLoading = true
showError = false
@@ -50,10 +61,42 @@ class LoginViewModel: ObservableObject {
}
}
- func logout() {
- username = ""
- password = ""
- isLoggedIn = false
+ func registerUser(username: String, password: String, invite: String?, completion: @escaping (Bool, String?) -> Void) {
+ authService.register(username: username, password: password, invite: invite) { [weak self] success, message in
+ DispatchQueue.main.async {
+ if success {
+ self?.isLoggedIn = true // 👈 переключаем на главный экран после автологина
+ }
+ completion(success, message)
+ }
+ }
}
+ func logoutCurrentUser() {
+ authService.logoutCurrentUser { [weak self] success, error in
+ DispatchQueue.main.async {
+ if success {
+ self?.username = UserDefaults.standard.string(forKey: "currentUser") ?? ""
+ self?.password = ""
+ self?.isLoggedIn = true
+ self?.showError = false
+ } else {
+ self?.username = ""
+ self?.password = ""
+ self?.isLoggedIn = false
+ self?.errorMessage = error ?? "Ошибка при деавторизации."
+ self?.showError = false
+ }
+ }
+ }
+ }
+
+
+// func logout() {
+// username = ""
+// password = ""
+// isLoggedIn = false
+// showError = false
+// errorMessage = ""
+// }
}
diff --git a/Shared/Views/CustomTextField.swift b/Shared/Views/CustomTextField.swift
new file mode 100644
index 0000000..f249cd1
--- /dev/null
+++ b/Shared/Views/CustomTextField.swift
@@ -0,0 +1,55 @@
+//
+// CustomTextField.swift
+// VolnahubApp
+//
+// Created by cheykrym on 11/06/2025.
+//
+
+import SwiftUI
+
+struct CustomTextField: UIViewRepresentable {
+ class Coordinator: NSObject, UITextFieldDelegate {
+ var parent: CustomTextField
+
+ init(_ parent: CustomTextField) {
+ self.parent = parent
+ }
+
+ func textFieldShouldReturn(_ textField: UITextField) -> Bool {
+ parent.onReturn()
+ return false // предотвращаем автоматический переход на новую строку
+ }
+
+ @objc func textFieldDidChange(_ textField: UITextField) {
+ parent.text = textField.text ?? ""
+ }
+ }
+
+ var placeholder: String
+ @Binding var text: String
+ var isSecure: Bool = false
+ var onReturn: () -> Void
+
+ func makeUIView(context: Context) -> UITextField {
+ let textField = UITextField()
+ textField.placeholder = placeholder
+ textField.delegate = context.coordinator
+ textField.borderStyle = .roundedRect
+ textField.autocapitalizationType = .none
+ textField.autocorrectionType = .no
+ textField.isSecureTextEntry = isSecure
+ textField.returnKeyType = .next
+ textField.addTarget(context.coordinator, action: #selector(Coordinator.textFieldDidChange(_:)), for: .editingChanged)
+ return textField
+ }
+
+ func updateUIView(_ uiView: UITextField, context: Context) {
+ if uiView.text != text {
+ uiView.text = text
+ }
+ }
+
+ func makeCoordinator() -> Coordinator {
+ Coordinator(self)
+ }
+}
diff --git a/Shared/Views/LoginView.swift b/Shared/Views/LoginView.swift
index ba1ff8b..936b6fd 100644
--- a/Shared/Views/LoginView.swift
+++ b/Shared/Views/LoginView.swift
@@ -24,115 +24,128 @@ struct LoginView: View {
var body: some View {
- VStack {
- HStack {
-
- Button(action: openLanguageSettings) {
- Text("🌍 " + NSLocalizedString("LoginView_change_language", comment: ""))
- .padding()
+ ZStack {
+ Color.clear // чтобы поймать тап
+ .contentShape(Rectangle())
+ .onTapGesture {
+ hideKeyboard()
}
- Spacer()
- Button(action: toggleTheme) {
- Image(systemName: isDarkMode ? "moon.fill" : "sun.max.fill")
- .padding()
- }
- }
- Spacer()
+ VStack {
+ HStack {
-
-// 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))
+ Button(action: openLanguageSettings) {
+ Text("🌍")
+ .padding()
+ }
+ Spacer()
+ Button(action: toggleTheme) {
+ Image(systemName: isDarkMode ? "moon.fill" : "sun.max.fill")
+ .padding()
}
}
-
- // Показываем ошибку для логина
- if !isUsernameValid && !viewModel.username.isEmpty {
- Text(NSLocalizedString("LoginView_error_username_invalid", comment: "Неверный логин"))
- .foregroundColor(.red)
- .font(.caption)
+ .onTapGesture {
+ hideKeyboard()
}
- // Показываем поле пароля (даже если оно невалидное) только если логин корректен
- if isUsernameValid {
- SecureField(NSLocalizedString("LoginView_password", comment: ""), text: $viewModel.password)
+ Spacer()
+
+ TextField(NSLocalizedString("LoginView_login", comment: ""), text: $viewModel.username)
.padding()
.background(Color(.secondarySystemBackground))
.cornerRadius(8)
.autocapitalization(.none)
- .onChange(of: viewModel.password) { newValue in
+ .disableAutocorrection(true)
+ .onChange(of: viewModel.username) { newValue in
if newValue.count > 32 {
- viewModel.password = String(newValue.prefix(32))
+ viewModel.username = String(newValue.prefix(32))
}
}
- // Показываем ошибку для пароля
- if !isPasswordValid && !viewModel.password.isEmpty {
- Text(NSLocalizedString("LoginView_error_password_invalid", comment: "Неверный пароль"))
+ // Показываем ошибку для логина
+ if !isUsernameValid && !viewModel.username.isEmpty {
+ Text(NSLocalizedString("LoginView_error_username_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)
+
+ // Показываем поле пароля (даже если оно невалидное) только если логин корректен
+ if isUsernameValid || !viewModel.password.isEmpty {
+ 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)
}
}
- .disabled(viewModel.isLoading)
- }
+
+ 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 || !isUsernameValid || !isPasswordValid)
+ }
- Spacer()
-
- // Кнопка регистрации
- Button(action: {
- isShowingRegistration = true
- }) {
- Text(NSLocalizedString("LoginView_button_register", comment: "Регистрация"))
- .foregroundColor(.blue)
+ Spacer()
+
+ // Кнопка регистрации
+ Button(action: {
+ isShowingRegistration = true
+ }) {
+ Text(NSLocalizedString("LoginView_button_register", comment: "Регистрация"))
+ .foregroundColor(.blue)
+ }
+ .padding(.top, 10)
+ .sheet(isPresented: $isShowingRegistration) {
+ RegistrationView(viewModel: viewModel)
+ }
+
}
- .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(NSLocalizedString("ok", comment: "")))
+ )
}
-
+ .onTapGesture {
+ hideKeyboard()
}
- .padding()
- .alert(isPresented: $viewModel.showError) {
- Alert(
- title: Text(NSLocalizedString("LoginView_error", comment: "")),
- message: Text(viewModel.errorMessage),
- dismissButton: .default(Text("ОК"))
- )
}
}
+
+
+
+
private func toggleTheme() {
isDarkMode.toggle()
}
@@ -141,6 +154,10 @@ struct LoginView: View {
guard let url = URL(string: UIApplication.openSettingsURLString) else { return }
UIApplication.shared.open(url)
}
+
+ private func hideKeyboard() {
+ UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
+ }
}
diff --git a/Shared/Views/MainView.swift b/Shared/Views/MainView.swift
index d285059..8f742fd 100644
--- a/Shared/Views/MainView.swift
+++ b/Shared/Views/MainView.swift
@@ -16,21 +16,21 @@ struct MainView: View {
ContactsTab()
.tabItem {
Image(systemName: "person.2.fill")
- Text("Контакты")
+ Text(NSLocalizedString("MainView_contacts", comment: ""))
}
.tag(0)
ChatsTab()
.tabItem {
Image(systemName: "bubble.left.and.bubble.right.fill")
- Text("Чаты")
+ Text(NSLocalizedString("MainView_chats", comment: ""))
}
.tag(1)
- SettingsTab()
+ SettingsTab(viewModel: viewModel)
.tabItem {
Image(systemName: "gearshape.fill")
- Text("Настройки")
+ Text(NSLocalizedString("MainView_settings", comment: ""))
}
.tag(2)
}
diff --git a/Shared/Views/RegistrationView.swift b/Shared/Views/RegistrationView.swift
index ba60051..fe1aa7e 100644
--- a/Shared/Views/RegistrationView.swift
+++ b/Shared/Views/RegistrationView.swift
@@ -8,15 +8,19 @@
import SwiftUI
struct RegistrationView: View {
+ @ObservedObject var viewModel: LoginViewModel
@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
+ @State private var isLoading: Bool = false
+ @State private var showError: Bool = false
+ @State private var errorMessage: String = ""
+
private var isUsernameValid: Bool {
let pattern = "^[A-Za-z0-9_]{3,32}$"
return username.range(of: pattern, options: .regularExpression) != nil
@@ -31,104 +35,179 @@ struct RegistrationView: View {
}
private var isFormValid: Bool {
- !fullName.isEmpty && isUsernameValid && isPasswordValid && isConfirmPasswordValid
+ isUsernameValid && isPasswordValid && isConfirmPasswordValid
}
var body: some View {
NavigationView {
- VStack {
- Text(NSLocalizedString("RegistrationView_title", comment: "Регистрация"))
- .font(.largeTitle)
- .bold()
+ ScrollView {
+ ZStack {
+ Color.clear
+ .contentShape(Rectangle())
+ .onTapGesture {
+ hideKeyboard()
+ }
-// Spacer()
+ VStack(alignment: .leading, spacing: 16) {
- // Полное имя
- TextField(NSLocalizedString("RegistrationView_fullname", comment: "Полное имя"), text: $fullName)
- .padding()
- .background(Color(.secondarySystemBackground))
- .cornerRadius(8)
- .autocapitalization(.words)
- .disableAutocorrection(true)
+ Group {
- // Логин
- 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)
+ HStack {
+ TextField(NSLocalizedString("RegistrationView_login", comment: "Логин"), text: $username)
+ .autocapitalization(.none)
+ .disableAutocorrection(true)
+ Spacer()
+ if !username.isEmpty {
+ Image(systemName: isUsernameValid ? "checkmark.circle" : "xmark.circle")
+ .foregroundColor(isUsernameValid ? .green : .red)
+ }
+ }
.padding()
- .frame(maxWidth: .infinity)
- .background(isFormValid ? Color.blue : Color.gray.opacity(0.6))
+ .background(Color(.secondarySystemBackground))
.cornerRadius(8)
- }
- .disabled(!isFormValid)
+ .autocapitalization(.none)
+ .disableAutocorrection(true)
+ .onChange(of: username) { newValue in
+ if newValue.count > 32 {
+ username = String(newValue.prefix(32))
+ }
+ }
- }
- .padding()
- .navigationBarItems(trailing:
- Button(action: {
- presentationMode.wrappedValue.dismiss()
- }) {
- Text(NSLocalizedString("RegistrationView_close", comment: "Закрыть"))
+ if !isUsernameValid && !username.isEmpty {
+ Text(NSLocalizedString("RegistrationView_error_username_invalid", comment: "Неверный логин"))
+ .foregroundColor(.red)
+ .font(.caption)
+ }
+
+ HStack {
+ SecureField(NSLocalizedString("RegistrationView_password", comment: "Пароль"), text: $password)
+ .autocapitalization(.none)
+ Spacer()
+ if !password.isEmpty {
+ Image(systemName: isPasswordValid ? "checkmark.circle" : "xmark.circle")
+ .foregroundColor(isPasswordValid ? .green : .red)
+ }
+ }
+ .padding()
+ .background(Color(.secondarySystemBackground))
+ .cornerRadius(8)
+ .autocapitalization(.none)
+ .onChange(of: password) { newValue in
+ if newValue.count > 32 {
+ password = String(newValue.prefix(32))
+ }
+ }
+
+ if !isPasswordValid && !password.isEmpty {
+ Text(NSLocalizedString("RegistrationView_error_password_invalid", comment: "Пароль должен быть от 6 до 32 символов"))
+ .foregroundColor(.red)
+ .font(.caption)
+ }
+
+ HStack {
+ SecureField(NSLocalizedString("RegistrationView_confirm_password", comment: "Подтверждение пароля"), text: $confirmPassword)
+ .autocapitalization(.none)
+ Spacer()
+ if !confirmPassword.isEmpty {
+ Image(systemName: isConfirmPasswordValid ? "checkmark.circle" : "xmark.circle")
+ .foregroundColor(isConfirmPasswordValid ? .green : .red)
+ }
+ }
+ .padding()
+ .background(Color(.secondarySystemBackground))
+ .cornerRadius(8)
+ .autocapitalization(.none)
+ .onChange(of: confirmPassword) { newValue in
+ if newValue.count > 32 {
+ confirmPassword = String(newValue.prefix(32))
+ }
+ }
+
+ 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: registerUser) {
+ if isLoading {
+ ProgressView()
+ .padding()
+ .frame(maxWidth: .infinity)
+ .background(Color.gray.opacity(0.6))
+ .cornerRadius(8)
+ } else {
+ 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(.bottom)
}
- )
+ .padding()
+ }
+ .navigationBarItems(trailing:
+ Button(action: {
+ presentationMode.wrappedValue.dismiss()
+ }) {
+ Text(NSLocalizedString("RegistrationView_close", comment: "Закрыть"))
+ }
+ )
+ .navigationTitle(Text(NSLocalizedString("RegistrationView_title", comment: "Регистрация")))
+ .alert(isPresented: $showError) {
+ Alert(
+ title: Text(NSLocalizedString("RegistrationView_error", comment: "Ошибка")),
+ message: Text(errorMessage),
+ dismissButton: .default(Text(NSLocalizedString("ok", comment: "")))
+ )
+ }
+ }
+ .onTapGesture {
+ hideKeyboard()
+ }
+ }
+ .onTapGesture {
+ hideKeyboard()
+ }
+ }
+
+ private func registerUser() {
+ isLoading = true
+ errorMessage = ""
+ viewModel.registerUser(username: username, password: password, invite: inviteCode.isEmpty ? nil : inviteCode) { success, message in
+ isLoading = false
+ if success {
+ presentationMode.wrappedValue.dismiss()
+ } else {
+ errorMessage = message ?? "Неизвестная ошибка."
+ showError = true
+ }
}
}
+ private func hideKeyboard() {
+ UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
+ }
}
+
struct RegistrationView_Previews: PreviewProvider {
static var previews: some View {
- RegistrationView()
+ let viewModel = LoginViewModel()
+ viewModel.isLoading = false // чтобы убрать спиннер
+ return RegistrationView(viewModel: viewModel)
}
}
diff --git a/Shared/Views/SettingsTab.swift b/Shared/Views/SettingsTab.swift
index e014cc8..777e34f 100644
--- a/Shared/Views/SettingsTab.swift
+++ b/Shared/Views/SettingsTab.swift
@@ -8,6 +8,8 @@
import SwiftUI
struct SettingsTab: View {
+ @ObservedObject var viewModel: LoginViewModel
+
var body: some View {
NavigationView {
VStack {
@@ -17,6 +19,15 @@ struct SettingsTab: View {
.padding()
Spacer()
+
+ Button(action: {
+ viewModel.logoutCurrentUser()
+ }) {
+ Text("Выйти из текущего пользователя")
+ .foregroundColor(.red)
+ }
+ .padding()
+
}
.navigationTitle("Настройки")
}
diff --git a/Shared/config.swift b/Shared/config.swift
index a098dfb..f0c30c8 100644
--- a/Shared/config.swift
+++ b/Shared/config.swift
@@ -1,9 +1,12 @@
import SwiftUI
struct AppConfig {
- static var DEBUG: Bool = false
+ static var DEBUG: Bool = true
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"
+ static let USER_AGENT = "volnahub ios"
+ static let APP_BUILD = "freestore"
+ static let APP_VERSION = "0.1"
}
diff --git a/Shared/volnahubApp.swift b/Shared/volnahubApp.swift
index 5218d59..fa5ad06 100644
--- a/Shared/volnahubApp.swift
+++ b/Shared/volnahubApp.swift
@@ -15,7 +15,7 @@ import SwiftUI
struct volnahubApp: App {
@AppStorage("isDarkMode") private var isDarkMode: Bool = true
@StateObject private var viewModel = LoginViewModel()
-
+
var body: some Scene {
WindowGroup {
ZStack {
diff --git a/iOS/Info.plist b/iOS/Info.plist
index 8cec9d0..bb81a82 100644
--- a/iOS/Info.plist
+++ b/iOS/Info.plist
@@ -15,7 +15,7 @@
CFBundlePackageType
$(PRODUCT_BUNDLE_PACKAGE_TYPE)
CFBundleShortVersionString
- 1.0
+ $(MARKETING_VERSION)
CFBundleVersion
$(CURRENT_PROJECT_VERSION)
LSRequiresIPhoneOS
diff --git a/volnahub.xcodeproj/project.pbxproj b/volnahub.xcodeproj/project.pbxproj
index 5eefaad..817ffd8 100644
--- a/volnahub.xcodeproj/project.pbxproj
+++ b/volnahub.xcodeproj/project.pbxproj
@@ -7,6 +7,8 @@
objects = {
/* Begin PBXBuildFile section */
+ 1A0276032DF909F900D8BC53 /* refreshtokenex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A0276022DF909F900D8BC53 /* refreshtokenex.swift */; };
+ 1A0276112DF9247000D8BC53 /* CustomTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A0276102DF9247000D8BC53 /* CustomTextField.swift */; };
1A79408D2DF77BC3002569DA /* 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 */; };
@@ -28,6 +30,8 @@
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
+ 1A0276022DF909F900D8BC53 /* refreshtokenex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = refreshtokenex.swift; sourceTree = ""; };
+ 1A0276102DF9247000D8BC53 /* CustomTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTextField.swift; sourceTree = ""; };
1A79407A2DF77BC2002569DA /* volnahubApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = volnahubApp.swift; sourceTree = ""; };
1A79407B2DF77BC2002569DA /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; };
1A79407C2DF77BC3002569DA /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
@@ -133,6 +137,7 @@
1A7940CD2DF7A9AA002569DA /* SettingsTab.swift */,
1A7940E62DF7B5E5002569DA /* SplashScreenView.swift */,
1A79410B2DF7C81D002569DA /* RegistrationView.swift */,
+ 1A0276102DF9247000D8BC53 /* CustomTextField.swift */,
);
path = Views;
sourceTree = "";
@@ -157,6 +162,7 @@
isa = PBXGroup;
children = (
1A7940A12DF77DE9002569DA /* AuthService.swift */,
+ 1A0276022DF909F900D8BC53 /* refreshtokenex.swift */,
);
path = Network;
sourceTree = "";
@@ -280,8 +286,10 @@
1A7940E72DF7B5E5002569DA /* SplashScreenView.swift in Sources */,
1A7940B02DF77E26002569DA /* LoginView.swift in Sources */,
1A79410C2DF7C81D002569DA /* RegistrationView.swift in Sources */,
+ 1A0276112DF9247000D8BC53 /* CustomTextField.swift in Sources */,
1A7940CE2DF7A9AA002569DA /* SettingsTab.swift in Sources */,
1A7940DE2DF7B0D7002569DA /* config.swift in Sources */,
+ 1A0276032DF909F900D8BC53 /* refreshtokenex.swift in Sources */,
1A7940B62DF77F21002569DA /* MainView.swift in Sources */,
1A7940E22DF7B1C5002569DA /* KeychainService.swift in Sources */,
1A7940A62DF77DF5002569DA /* User.swift in Sources */,
@@ -434,8 +442,10 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1337;
+ DEVELOPMENT_TEAM = V22H44W47J;
ENABLE_PREVIEWS = YES;
INFOPLIST_FILE = iOS/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
@@ -443,8 +453,10 @@
"$(inherited)",
"@executable_path/Frameworks",
);
- PRODUCT_BUNDLE_IDENTIFIER = ckp.volnahub;
+ MARKETING_VERSION = 0.1;
+ PRODUCT_BUNDLE_IDENTIFIER = ckp2.volnahub;
PRODUCT_NAME = volnahub;
+ PROVISIONING_PROFILE_SPECIFIER = "";
SDKROOT = iphoneos;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
@@ -456,8 +468,10 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1337;
+ DEVELOPMENT_TEAM = V22H44W47J;
ENABLE_PREVIEWS = YES;
INFOPLIST_FILE = iOS/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
@@ -465,8 +479,10 @@
"$(inherited)",
"@executable_path/Frameworks",
);
- PRODUCT_BUNDLE_IDENTIFIER = ckp.volnahub;
+ MARKETING_VERSION = 0.1;
+ PRODUCT_BUNDLE_IDENTIFIER = ckp2.volnahub;
PRODUCT_NAME = volnahub;
+ PROVISIONING_PROFILE_SPECIFIER = "";
SDKROOT = iphoneos;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
diff --git a/volnahub.xcodeproj/xcuserdata/cheykrym.xcuserdatad/xcschemes/xcschememanagement.plist b/volnahub.xcodeproj/xcuserdata/cheykrym.xcuserdatad/xcschemes/xcschememanagement.plist
index 6cdd854..32667f1 100644
--- a/volnahub.xcodeproj/xcuserdata/cheykrym.xcuserdatad/xcschemes/xcschememanagement.plist
+++ b/volnahub.xcodeproj/xcuserdata/cheykrym.xcuserdatad/xcschemes/xcschememanagement.plist
@@ -7,12 +7,12 @@
volnahub (iOS).xcscheme_^#shared#^_
orderHint
- 1
+ 0
volnahub (macOS).xcscheme_^#shared#^_
orderHint
- 0
+ 1