ios_app_v2/yobble/ViewModels/LoginViewModel.swift
2025-10-24 11:03:27 +03:00

232 lines
7.9 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// LoginViewModel.swift
// VolnahubApp
//
// Created by cheykrym on 09/06/2025.
//
import Foundation
import Combine
class LoginViewModel: ObservableObject {
@Published var username: String = ""
@Published var userId: 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
@Published var socketState: SocketService.ConnectionState
@Published var chatLoadingState: ChatLoadingState = .idle
@Published var hasAcceptedTerms: Bool = false
@Published var isLoadingTerms: Bool = false
@Published var termsContent: String = ""
@Published var termsErrorMessage: String?
@Published var onboardingDestination: OnboardingDestination?
private let authService = AuthService()
private let socketService = SocketService.shared
private var cancellables = Set<AnyCancellable>()
enum ChatLoadingState: Equatable {
case idle
case loading
}
enum OnboardingDestination: Equatable {
case twoFactor
}
private enum DefaultsKeys {
static let currentUser = "currentUser"
static let userId = "userId"
}
init() {
socketState = socketService.currentConnectionState
observeSocketState()
observeChatsReload()
// loadStoredUser()
// Запускаем автологин
autoLogin()
}
private func observeSocketState() {
socketService.connectionStatePublisher
.receive(on: DispatchQueue.main)
.sink { [weak self] state in
self?.handleSocketStateChange(state)
}
.store(in: &cancellables)
}
private func observeChatsReload() {
NotificationCenter.default.publisher(for: .chatsReloadCompleted)
.receive(on: DispatchQueue.main)
.sink { [weak self] _ in
self?.chatLoadingState = .idle
}
.store(in: &cancellables)
}
private func handleSocketStateChange(_ state: SocketService.ConnectionState) {
socketState = state
if state == .connected {
triggerChatsReloadIfNeeded()
} else {
chatLoadingState = .idle
}
}
private func triggerChatsReloadIfNeeded() {
guard chatLoadingState != .loading else { return }
chatLoadingState = .loading
NotificationCenter.default.post(name: .chatsShouldRefresh, object: nil)
}
func autoLogin() {
authService.autoLogin { [weak self] success, error in
DispatchQueue.main.async {
// self?.isLoading = false
if success {
self?.loadStoredUser()
self?.isLoggedIn = true
self?.socketService.connectForCurrentUser()
} else {
self?.isLoggedIn = false
self?.errorMessage = error ?? NSLocalizedString("Произошла ошибка.", comment: "")
self?.showError = false
self?.socketService.disconnect()
}
self?.isLoading = false
}
}
}
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?.loadStoredUser()
self?.isLoggedIn = true
self?.socketService.connectForCurrentUser()
} else {
self?.errorMessage = error ?? NSLocalizedString("Неизвестная ошибка", comment: "")
self?.showError = true
self?.socketService.disconnect()
}
}
}
}
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 // 👈 переключаем на главный экран после автологина
self?.loadStoredUser()
self?.socketService.connectForCurrentUser()
self?.onboardingDestination = .twoFactor
} else {
self?.socketService.disconnect()
}
completion(success, message)
}
}
}
func logoutCurrentUser() {
authService.logoutCurrentUser { [weak self] success, error in
DispatchQueue.main.async {
if success {
self?.loadStoredUser()
self?.password = ""
self?.isLoggedIn = true
self?.showError = false
self?.socketService.connectForCurrentUser()
} else {
self?.username = ""
self?.userId = ""
self?.password = ""
self?.isLoggedIn = false
self?.errorMessage = error ?? NSLocalizedString("Ошибка при деавторизации.", comment: "")
self?.showError = false
self?.socketService.disconnect()
}
}
}
}
// func logout() {
// username = ""
// password = ""
// isLoggedIn = false
// showError = false
// errorMessage = ""
// }
private func loadStoredUser() {
let defaults = UserDefaults.standard
username = defaults.string(forKey: DefaultsKeys.currentUser) ?? ""
userId = KeychainService.shared.get(forKey: DefaultsKeys.userId, service: username) ?? ""
if AppConfig.DEBUG{ print("username: \(username) | userId: \(userId)")}
}
func loadTermsIfNeeded() {
guard !isLoadingTerms else { return }
if !termsContent.isEmpty {
termsErrorMessage = nil
return
}
isLoadingTerms = true
termsErrorMessage = nil
NetworkClient.shared.request(
path: "/legal/terms",
headers: ["Accept": "text/plain"],
requiresAuth: false,
callbackQueue: .main
) { [weak self] result in
guard let self else { return }
self.isLoadingTerms = false
switch result {
case .success(let response):
if let content = String(data: response.data, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines),
!content.isEmpty {
self.termsContent = content
return
}
if let jsonObject = try? JSONSerialization.jsonObject(with: response.data, options: []),
let json = jsonObject as? [String: Any],
let content = (json["content"] as? String) ?? (json["text"] as? String),
!content.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
self.termsContent = content
} else {
self.termsErrorMessage = NSLocalizedString("Не удалось загрузить текст правил.", comment: "")
}
case .failure:
self.termsErrorMessage = NSLocalizedString("Не удалось загрузить текст правил.", comment: "")
}
}
}
func reloadTerms() {
termsContent = ""
termsErrorMessage = nil
loadTermsIfNeeded()
}
}