171 lines
6.2 KiB
Swift
171 lines
6.2 KiB
Swift
//
|
||
// 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 >= 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: isDarkMode ? "moon.fill" : "sun.max.fill")
|
||
.padding()
|
||
}
|
||
}
|
||
.onTapGesture {
|
||
hideKeyboard()
|
||
}
|
||
|
||
Spacer()
|
||
|
||
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 || !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)
|
||
}
|
||
}
|
||
|
||
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)
|
||
}
|
||
.padding(.top, 10)
|
||
.sheet(isPresented: $isShowingRegistration) {
|
||
RegistrationView(viewModel: viewModel)
|
||
}
|
||
|
||
}
|
||
.padding()
|
||
.alert(isPresented: $viewModel.showError) {
|
||
Alert(
|
||
title: Text(NSLocalizedString("LoginView_error", comment: "")),
|
||
message: Text(viewModel.errorMessage),
|
||
dismissButton: .default(Text(NSLocalizedString("ok", comment: "")))
|
||
)
|
||
}
|
||
.onTapGesture {
|
||
hideKeyboard()
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
private func toggleTheme() {
|
||
isDarkMode.toggle()
|
||
}
|
||
|
||
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)
|
||
}
|
||
}
|