185 lines
6.4 KiB
Swift
185 lines
6.4 KiB
Swift
//
|
||
// LoginView.swift
|
||
// VolnahubApp
|
||
//
|
||
// Created by cheykrym on 09/06/2025.
|
||
//
|
||
|
||
import SwiftUI
|
||
|
||
struct LoginView: View {
|
||
@ObservedObject var viewModel: LoginViewModel
|
||
@EnvironmentObject private var themeManager: ThemeManager
|
||
@Environment(\.colorScheme) private var colorScheme
|
||
|
||
@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 >= 8 && viewModel.password.count <= 128
|
||
}
|
||
|
||
var body: some View {
|
||
|
||
ZStack {
|
||
Color.clear // чтобы поймать тап
|
||
.contentShape(Rectangle())
|
||
.onTapGesture {
|
||
hideKeyboard()
|
||
}
|
||
|
||
VStack {
|
||
HStack {
|
||
|
||
Button(action: openLanguageSettings) {
|
||
Text("🌍")
|
||
.padding()
|
||
}
|
||
Spacer()
|
||
Button(action: toggleTheme) {
|
||
Image(systemName: themeIconName)
|
||
.padding()
|
||
}
|
||
}
|
||
.onTapGesture {
|
||
hideKeyboard()
|
||
}
|
||
|
||
Spacer()
|
||
|
||
TextField(NSLocalizedString("Логин", 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("Неверный логин", comment: "Неверный логин"))
|
||
.foregroundColor(.red)
|
||
.font(.caption)
|
||
}
|
||
|
||
// Показываем поле пароля
|
||
SecureField(NSLocalizedString("Пароль", 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("Неверный пароль", comment: "Неверный пароль"))
|
||
.foregroundColor(.red)
|
||
.font(.caption)
|
||
}
|
||
|
||
var isButtonEnabled: Bool {
|
||
!viewModel.isLoading && 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("Войти", comment: ""))
|
||
.foregroundColor(.white)
|
||
.padding()
|
||
.frame(maxWidth: .infinity)
|
||
.background(isButtonEnabled ? Color.blue : Color.gray)
|
||
.cornerRadius(8)
|
||
}
|
||
}
|
||
.disabled(!isButtonEnabled)
|
||
|
||
// Spacer()
|
||
|
||
// Кнопка регистрации
|
||
Button(action: {
|
||
isShowingRegistration = true
|
||
}) {
|
||
Text(NSLocalizedString("Нет аккаунта? Регистрация", comment: "Регистрация"))
|
||
.foregroundColor(.blue)
|
||
}
|
||
.padding(.top, 10)
|
||
.sheet(isPresented: $isShowingRegistration) {
|
||
RegistrationView(viewModel: viewModel, isPresented: $isShowingRegistration)
|
||
}
|
||
|
||
Spacer()
|
||
|
||
}
|
||
.padding()
|
||
.alert(isPresented: $viewModel.showError) {
|
||
Alert(
|
||
title: Text(NSLocalizedString("Ошибка авторизации", comment: "")),
|
||
message: Text(viewModel.errorMessage),
|
||
dismissButton: .default(Text(NSLocalizedString("OK", comment: "")))
|
||
)
|
||
}
|
||
.onTapGesture {
|
||
hideKeyboard()
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
private var themeIconName: String {
|
||
switch themeManager.theme {
|
||
case .system:
|
||
return colorScheme == .dark ? "moon.fill" : "sun.max.fill"
|
||
case .light:
|
||
return "sun.max.fill"
|
||
case .dark:
|
||
return "moon.fill"
|
||
}
|
||
}
|
||
|
||
private func toggleTheme() {
|
||
themeManager.toggleTheme(from: colorScheme)
|
||
}
|
||
|
||
private func openLanguageSettings() {
|
||
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)
|
||
}
|
||
|
||
}
|
||
|
||
struct LoginView_Previews: PreviewProvider {
|
||
static var previews: some View {
|
||
let viewModel = LoginViewModel()
|
||
viewModel.isLoading = false // чтобы убрать спиннер
|
||
return LoginView(viewModel: viewModel)
|
||
}
|
||
}
|