412 lines
16 KiB
Python
412 lines
16 KiB
Python
from PySide6.QtWidgets import (
|
|
QWidget, QLabel, QLineEdit, QPushButton, QVBoxLayout, QMessageBox,
|
|
QHBoxLayout, QSpacerItem, QSizePolicy, QComboBox
|
|
)
|
|
from PySide6.QtCore import Qt, QTimer
|
|
from ..widgets.validation_input import ValidationInput
|
|
from common_lib.utils.validators import (
|
|
validate_username as common_validate_username,
|
|
validate_password as common_validate_password
|
|
)
|
|
from app.core.localizer import localizer
|
|
from app.core.theme import theme_manager
|
|
import app.core.config as config
|
|
import asyncio
|
|
from app.core.services import auth_service
|
|
|
|
|
|
def validate_username(username, is_login=False):
|
|
if is_login:
|
|
if len(username) < 3 or len(username) > 32:
|
|
msg = localizer.translate("Неверный логин или пароль")
|
|
return False, msg
|
|
return True, username
|
|
|
|
is_validate, msg = common_validate_username(username, need_back=True)
|
|
if not is_validate: msg = localizer.translate(msg)
|
|
return is_validate, msg
|
|
|
|
def validate_invite_code(invite_code):
|
|
is_validate, msg = common_validate_username(invite_code, field_name="invite", need_back=True)
|
|
if not is_validate: msg = localizer.translate(msg)
|
|
return is_validate, msg
|
|
|
|
|
|
def validate_password(password, is_login=False):
|
|
if is_login:
|
|
if len(password) < 8 or len(password) > 128:
|
|
msg = localizer.translate("Неверный логин или пароль")
|
|
return False, msg
|
|
return True, password
|
|
|
|
is_validate, msg = common_validate_password(password, need_back=True)
|
|
if not is_validate: msg = localizer.translate(msg)
|
|
return is_validate, msg
|
|
|
|
# def validate_name(name):
|
|
# # Optional field
|
|
# if not name:
|
|
# return True, ""
|
|
# if len(name) >= 32:
|
|
# return False, "Имя не должно превышать 32 символов"
|
|
# return True, ""
|
|
|
|
|
|
class LoginView(QWidget):
|
|
def __init__(self, on_login):
|
|
super().__init__()
|
|
self.on_login = on_login
|
|
self.is_dark_theme = theme_manager.is_dark()
|
|
print("self.is_dark_theme", self.is_dark_theme)
|
|
# self.setWindowTitle(config.APP_HEADER)
|
|
# # self.setFixedSize(400, 650)
|
|
# self.setMinimumSize(400, 650)
|
|
|
|
self.lang_combo = None
|
|
self.is_registration = False
|
|
|
|
self.init_ui()
|
|
if self.is_dark_theme:
|
|
self.apply_dark_theme()
|
|
else:
|
|
self.apply_light_theme()
|
|
|
|
def init_ui(self):
|
|
# Переключатель темы и языка
|
|
theme_layout = QHBoxLayout()
|
|
theme_layout.setAlignment(Qt.AlignRight)
|
|
self.theme_button = QPushButton("🌞")
|
|
self.theme_button.setFixedWidth(50)
|
|
self.theme_button.clicked.connect(self.toggle_theme)
|
|
|
|
self.lang_combo = QComboBox()
|
|
self.lang_combo.setFixedWidth(100)
|
|
self.lang_map = {} # display_name: lang_code
|
|
|
|
for lang_code, lang_name in localizer.get_available_languages():
|
|
self.lang_map[lang_name] = lang_code
|
|
self.lang_combo.addItem(lang_name)
|
|
|
|
# ✅ Устанавливаем текущий язык по индексу
|
|
for i in range(self.lang_combo.count()):
|
|
item_text = self.lang_combo.itemText(i)
|
|
if self.lang_map.get(item_text) == localizer.lang:
|
|
self.lang_combo.setCurrentIndex(i)
|
|
break
|
|
|
|
print("localizer.lang", localizer.lang)
|
|
|
|
self.lang_combo.currentTextChanged.connect(self.change_language)
|
|
|
|
theme_layout.addSpacerItem(QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum))
|
|
theme_layout.addWidget(self.lang_combo)
|
|
theme_layout.addWidget(self.theme_button)
|
|
|
|
# Основная часть
|
|
self.main_layout = QVBoxLayout()
|
|
self.main_layout.setAlignment(Qt.AlignCenter)
|
|
self.main_layout.setContentsMargins(40, 40, 40, 40)
|
|
|
|
self.title = QLabel(localizer.translate("Авторизация"))
|
|
self.title.setAlignment(Qt.AlignCenter)
|
|
self.title.setStyleSheet("font-size: 20px; font-weight: bold;")
|
|
self.main_layout.addWidget(self.title)
|
|
self.main_layout.addSpacing(20)
|
|
|
|
self.init_login_form()
|
|
self.init_register_form()
|
|
self.show_login_form()
|
|
|
|
# Компоновка
|
|
full_layout = QVBoxLayout(self)
|
|
full_layout.addLayout(theme_layout)
|
|
full_layout.addStretch()
|
|
full_layout.addLayout(self.main_layout)
|
|
full_layout.addStretch()
|
|
self.setLayout(full_layout)
|
|
|
|
def init_login_form(self):
|
|
self.login_input = QLineEdit()
|
|
self.login_input.setPlaceholderText(localizer.translate("Логин"))
|
|
self.login_input.setFixedHeight(40)
|
|
|
|
self.password_input = QLineEdit()
|
|
self.password_input.setPlaceholderText(localizer.translate("Пароль"))
|
|
self.password_input.setEchoMode(QLineEdit.Password)
|
|
self.password_input.setFixedHeight(40)
|
|
|
|
self.login_button = QPushButton(localizer.translate("Войти"))
|
|
self.login_button.setFixedHeight(40)
|
|
self.login_button.clicked.connect(self._handle_login_click)
|
|
|
|
self.register_switch = QPushButton(localizer.translate("Нет аккаунта? Регистрация"))
|
|
self.register_switch.setFlat(True)
|
|
self.register_switch.clicked.connect(self.show_register_form)
|
|
|
|
def init_register_form(self):
|
|
# self.name_input = ValidationInput("Имя")
|
|
# self.name_input.set_validator(validate_name, is_required=False)
|
|
|
|
self.reg_login_input = ValidationInput(localizer.translate("Логин"))
|
|
self.reg_login_input.set_validator(validate_username)
|
|
|
|
self.reg_password_input = ValidationInput(localizer.translate("Пароль"), is_password=True)
|
|
self.reg_password_input.set_validator(validate_password)
|
|
|
|
self.confirm_password_input = ValidationInput(localizer.translate("Повторите пароль"), is_password=True)
|
|
self.confirm_password_input.set_validator(self.validate_confirm_password)
|
|
|
|
self.invite_code_input = ValidationInput(localizer.translate("Инвайт-код"))
|
|
self.invite_code_input.set_validator(validate_invite_code, is_required=False)
|
|
|
|
self.register_button = QPushButton(localizer.translate("Зарегистрироваться"))
|
|
self.register_button.setFixedHeight(40)
|
|
self.register_button.clicked.connect(self._handle_register_click)
|
|
|
|
self.login_switch = QPushButton(localizer.translate("Уже есть аккаунт? Войти"))
|
|
self.login_switch.setFlat(True)
|
|
self.login_switch.clicked.connect(self.show_login_form)
|
|
|
|
self.reg_password_input.textChanged.connect(self.confirm_password_input.on_text_changed)
|
|
|
|
def _update_window_title(self):
|
|
def set_title():
|
|
window = self.window()
|
|
if not window:
|
|
return
|
|
|
|
if self.is_registration:
|
|
title = f"{localizer.translate('Регистрация')} | {config.APP_HEADER}"
|
|
else:
|
|
title = f"{localizer.translate('Авторизация')} | {config.APP_HEADER}"
|
|
window.setWindowTitle(title)
|
|
|
|
# Откладываем выполнение, чтобы `self.window()` уже был готов
|
|
QTimer.singleShot(0, set_title)
|
|
|
|
def render_form(self):
|
|
self.clear_form()
|
|
self.clear_main_layout()
|
|
self._update_window_title()
|
|
|
|
if self.is_registration:
|
|
self.title.setText(localizer.translate("Регистрация"))
|
|
|
|
self.register_button.setText(localizer.translate("Зарегистрироваться"))
|
|
self.login_switch.setText(localizer.translate("Уже есть аккаунт? Войти"))
|
|
|
|
self.reg_login_input.input.setPlaceholderText(localizer.translate("Логин"))
|
|
self.reg_password_input.input.setPlaceholderText(localizer.translate("Пароль"))
|
|
self.confirm_password_input.input.setPlaceholderText(localizer.translate("Повторите пароль"))
|
|
self.invite_code_input.input.setPlaceholderText(localizer.translate("Инвайт-код"))
|
|
|
|
self.main_layout.addWidget(self.reg_login_input)
|
|
self.main_layout.addWidget(self.reg_password_input)
|
|
self.main_layout.addWidget(self.confirm_password_input)
|
|
self.main_layout.addWidget(self.invite_code_input)
|
|
self.main_layout.addWidget(self.register_button)
|
|
self.main_layout.addSpacing(10)
|
|
self.main_layout.addWidget(self.login_switch)
|
|
else:
|
|
self.title.setText(localizer.translate("Авторизация"))
|
|
|
|
self.login_button.setText(localizer.translate("Войти"))
|
|
self.register_switch.setText(localizer.translate("Нет аккаунта? Регистрация"))
|
|
|
|
self.login_input.setPlaceholderText(localizer.translate("Логин"))
|
|
self.password_input.setPlaceholderText(localizer.translate("Пароль"))
|
|
|
|
self.main_layout.addWidget(self.login_input)
|
|
self.main_layout.addWidget(self.password_input)
|
|
self.main_layout.addWidget(self.login_button)
|
|
self.main_layout.addSpacing(10)
|
|
self.main_layout.addWidget(self.register_switch)
|
|
|
|
def show_login_form(self):
|
|
self.is_registration = False
|
|
self.render_form()
|
|
|
|
def show_register_form(self):
|
|
self.is_registration = True
|
|
self.render_form()
|
|
|
|
def clear_form(self):
|
|
self.login_input.clear()
|
|
self.password_input.clear()
|
|
# self.name_input.clear()
|
|
self.reg_login_input.clear()
|
|
self.reg_password_input.clear()
|
|
self.confirm_password_input.clear()
|
|
self.invite_code_input.clear()
|
|
|
|
def clear_main_layout(self):
|
|
while self.main_layout.count() > 2: # сохраняем title и spacing
|
|
child = self.main_layout.takeAt(2)
|
|
if child.widget():
|
|
child.widget().setParent(None)
|
|
|
|
def _handle_login_click(self):
|
|
"""Синхронный обработчик для сигнала нажатия кнопки."""
|
|
asyncio.create_task(self.handle_login())
|
|
|
|
async def handle_login(self):
|
|
login = self.login_input.text()
|
|
password = self.password_input.text()
|
|
|
|
is_login_valid, login_msg = validate_username(login, is_login=True)
|
|
is_password_valid, password_msg = validate_password(password, is_login=True)
|
|
|
|
if not is_login_valid or not is_password_valid:
|
|
# Показываем первую попавшуюся ошибку, они должны быть общими
|
|
error_msg = login_msg if not is_login_valid else password_msg
|
|
QMessageBox.warning(self, localizer.translate("Ошибка"), error_msg)
|
|
return
|
|
|
|
# Отключаем кнопку на время запроса
|
|
self.login_button.setEnabled(False)
|
|
self.login_button.setText(localizer.translate("Вход..."))
|
|
|
|
try:
|
|
# Запускаем асинхронную функцию
|
|
success, message = await auth_service.login(login, password)
|
|
|
|
if success:
|
|
self.on_login(login)
|
|
else:
|
|
QMessageBox.warning(self, localizer.translate("Ошибка"), message)
|
|
|
|
finally:
|
|
# Включаем кнопку обратно в любом случае
|
|
self.login_button.setEnabled(True)
|
|
self.login_button.setText(localizer.translate("Войти"))
|
|
|
|
|
|
def _handle_register_click(self):
|
|
"""Синхронный обработчик для сигнала нажатия кнопки."""
|
|
asyncio.create_task(self.handle_register())
|
|
|
|
def validate_confirm_password(self, text):
|
|
if text != self.reg_password_input.text():
|
|
return False, localizer.translate("Пароли не совпадают")
|
|
return True, ""
|
|
|
|
async def handle_register(self):
|
|
# Принудительная валидация всех полей
|
|
self.reg_login_input.on_text_changed(self.reg_login_input.text())
|
|
self.reg_password_input.on_text_changed(self.reg_password_input.text())
|
|
self.confirm_password_input.on_text_changed(self.confirm_password_input.text())
|
|
self.invite_code_input.on_text_changed(self.invite_code_input.text())
|
|
|
|
# Проверка, что все поля валидны
|
|
if not all([
|
|
self.reg_login_input.is_valid,
|
|
self.reg_password_input.is_valid,
|
|
self.confirm_password_input.is_valid,
|
|
self.invite_code_input.is_valid
|
|
]):
|
|
QMessageBox.warning(self, localizer.translate("Ошибка"), localizer.translate("Пожалуйста, исправьте ошибки в форме"))
|
|
return
|
|
|
|
login = self.reg_login_input.text()
|
|
password = self.reg_password_input.text()
|
|
invite = self.invite_code_input.text() or None # Отправляем None, если строка пустая
|
|
|
|
# Блокируем кнопку на время запроса
|
|
self.register_button.setEnabled(False)
|
|
self.register_button.setText(localizer.translate("Регистрация..."))
|
|
|
|
try:
|
|
success, message = await auth_service.register(login, password, invite)
|
|
|
|
if success:
|
|
QMessageBox.information(self, localizer.translate("Успех"), message)
|
|
self.show_login_form()
|
|
else:
|
|
QMessageBox.warning(self, localizer.translate("Ошибка"), message)
|
|
|
|
finally:
|
|
# Возвращаем кнопку в исходное состояние
|
|
self.register_button.setEnabled(True)
|
|
self.register_button.setText(localizer.translate("Зарегистрироваться"))
|
|
|
|
|
|
def change_language(self, display_name):
|
|
lang_code = self.lang_map.get(display_name)
|
|
print("lang_code", lang_code)
|
|
if lang_code:
|
|
localizer.switch_language(lang_code)
|
|
self.update_ui_language()
|
|
|
|
def toggle_theme(self):
|
|
self.is_dark_theme = not self.is_dark_theme
|
|
theme_manager.set_theme("dark" if self.is_dark_theme else "light")
|
|
if self.is_dark_theme:
|
|
self.apply_dark_theme()
|
|
else:
|
|
self.apply_light_theme()
|
|
|
|
def apply_dark_theme(self):
|
|
self.setStyleSheet("""
|
|
QWidget {
|
|
background-color: #2e2e2e;
|
|
color: white;
|
|
}
|
|
QLineEdit {
|
|
background-color: #444;
|
|
color: white;
|
|
border: 1px solid #666;
|
|
border-radius: 5px;
|
|
padding: 5px;
|
|
}
|
|
QPushButton {
|
|
background-color: #555;
|
|
color: white;
|
|
border: none;
|
|
border-radius: 5px;
|
|
}
|
|
QPushButton:hover {
|
|
background-color: #777;
|
|
}
|
|
QPushButton:flat {
|
|
background-color: transparent;
|
|
color: #aaa;
|
|
text-decoration: underline;
|
|
}
|
|
""")
|
|
self.theme_button.setText("🌙")
|
|
|
|
def apply_light_theme(self):
|
|
self.setStyleSheet("""
|
|
QWidget {
|
|
background-color: #f0f0f0;
|
|
color: #222;
|
|
}
|
|
QLineEdit {
|
|
background-color: white;
|
|
color: black;
|
|
border: 1px solid #ccc;
|
|
border-radius: 5px;
|
|
padding: 5px;
|
|
}
|
|
QPushButton {
|
|
background-color: #e0e0e0;
|
|
color: black;
|
|
border: none;
|
|
border-radius: 5px;
|
|
}
|
|
QPushButton:hover {
|
|
background-color: #d0d0d0;
|
|
}
|
|
QPushButton:flat {
|
|
background-color: transparent;
|
|
color: #666;
|
|
text-decoration: underline;
|
|
}
|
|
""")
|
|
self.theme_button.setText("🌞")
|
|
|
|
def update_ui_language(self):
|
|
self.theme_button.setText("🌞" if not self.is_dark_theme else "🌙")
|
|
self.render_form()
|
|
self._update_window_title()
|