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