This commit is contained in:
unknown 2025-09-06 05:10:09 +03:00
commit 38a7d08d57
20 changed files with 501 additions and 0 deletions

131
.gitignore vendored Normal file
View File

@ -0,0 +1,131 @@
#
config/cert/
test_modules/
config/SSL/fullchain.pem
config/SSL/privkey.pem
logs/
SECRET_KEY.key
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
.python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# celery beat schedule file
celerybeat-schedule
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/

1
README.md Normal file
View File

@ -0,0 +1 @@
# desktop_app

0
app/__init__.py Normal file
View File

View File

View File

@ -0,0 +1,16 @@
from app.ui.login_view import LoginView
from app.ui.chat_list_view import ChatListView
class MainController:
def __init__(self):
self.login_view = None
self.chat_list_view = None
def show_login(self):
self.login_view = LoginView(on_login=self.handle_login_success)
self.login_view.show()
def handle_login_success(self, username):
self.login_view.close()
self.chat_list_view = ChatListView(username=username)
self.chat_list_view.show()

0
app/core/__init__.py Normal file
View File

0
app/core/config.py Normal file
View File

21
app/core/database.py Normal file
View File

@ -0,0 +1,21 @@
import sqlite3
import os
DB_PATH = "messenger.db"
def get_connection():
return sqlite3.connect(DB_PATH)
def init_db():
if not os.path.exists(DB_PATH):
conn = get_connection()
cursor = conn.cursor()
cursor.execute('''
CREATE TABLE IF NOT EXISTS chats (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL
)
''')
cursor.execute('INSERT INTO chats (title) VALUES (?)', ("Чат с Alice",))
conn.commit()
conn.close()

View File

View File

0
app/ui/__init__.py Normal file
View File

23
app/ui/chat_list_view.py Normal file
View File

@ -0,0 +1,23 @@
from PySide6.QtWidgets import QWidget, QListWidget, QVBoxLayout, QLabel
class ChatListView(QWidget):
def __init__(self, username):
super().__init__()
self.setWindowTitle(f"Чаты — {username}")
self.setMinimumSize(400, 500)
self.init_ui()
def init_ui(self):
layout = QVBoxLayout()
self.label = QLabel("Список чатов:")
layout.addWidget(self.label)
self.chat_list = QListWidget()
# Для примера добавим 1 чат
self.chat_list.addItem("Чат с Alice")
self.chat_list.addItem("Чат с Bob")
layout.addWidget(self.chat_list)
self.setLayout(layout)

245
app/ui/login_view.py Normal file
View File

@ -0,0 +1,245 @@
from PySide6.QtWidgets import (
QWidget, QLabel, QLineEdit, QPushButton, QVBoxLayout, QMessageBox,
QHBoxLayout, QSpacerItem, QSizePolicy
)
from PySide6.QtCore import Qt
from common_lib.utils.validators import validate_username, validate_password
class LoginView(QWidget):
def __init__(self, on_login):
super().__init__()
self.on_login = on_login
self.setWindowTitle("yobble messenger")
self.setFixedSize(400, 550)
self.is_dark_theme = True
self.is_registration = False
self.init_ui()
self.apply_dark_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)
theme_layout.addSpacerItem(QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum))
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("Авторизация")
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("Логин")
self.login_input.setFixedHeight(40)
self.password_input = QLineEdit()
self.password_input.setPlaceholderText("Пароль")
self.password_input.setEchoMode(QLineEdit.Password)
self.password_input.setFixedHeight(40)
self.login_button = QPushButton("Войти")
self.login_button.setFixedHeight(40)
self.login_button.clicked.connect(self.handle_login)
self.register_switch = QPushButton("Нет аккаунта? Регистрация")
self.register_switch.setFlat(True)
self.register_switch.clicked.connect(self.show_register_form)
def init_register_form(self):
self.name_input = QLineEdit()
self.name_input.setPlaceholderText("Имя")
self.name_input.setFixedHeight(40)
self.reg_login_input = QLineEdit()
self.reg_login_input.setPlaceholderText("Логин")
self.reg_login_input.setFixedHeight(40)
self.reg_password_input = QLineEdit()
self.reg_password_input.setPlaceholderText("Пароль")
self.reg_password_input.setEchoMode(QLineEdit.Password)
self.reg_password_input.setFixedHeight(40)
self.confirm_password_input = QLineEdit()
self.confirm_password_input.setPlaceholderText("Повторите пароль")
self.confirm_password_input.setEchoMode(QLineEdit.Password)
self.confirm_password_input.setFixedHeight(40)
self.invite_code_input = QLineEdit()
self.invite_code_input.setPlaceholderText("Инвайт-код")
self.invite_code_input.setFixedHeight(40)
self.register_button = QPushButton("Зарегистрироваться")
self.register_button.setFixedHeight(40)
self.register_button.clicked.connect(self.handle_register)
self.login_switch = QPushButton("Уже есть аккаунт? Войти")
self.login_switch.setFlat(True)
self.login_switch.clicked.connect(self.show_login_form)
def show_login_form(self):
self.is_registration = False
self.title.setText("Авторизация")
self.clear_form()
# Очистка layout
self.clear_main_layout()
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_register_form(self):
self.is_registration = True
self.title.setText("Регистрация")
self.clear_form()
self.clear_main_layout()
self.main_layout.addWidget(self.name_input)
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)
def clear_form(self):
for field in [
self.login_input, self.password_input,
self.name_input, self.reg_login_input,
self.reg_password_input, self.confirm_password_input,
self.invite_code_input
]:
field.setText("")
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(self):
login = self.login_input.text()
password = self.password_input.text()
if login == "root" and password == "123":
self.on_login(login)
else:
QMessageBox.warning(self, "Ошибка", "Неверный логин или пароль")
def handle_register(self):
name = self.name_input.text()
login = self.reg_login_input.text()
password = self.reg_password_input.text()
confirm = self.confirm_password_input.text()
invite = self.invite_code_input.text()
if not all([name, login, password, confirm, invite]):
QMessageBox.warning(self, "Ошибка", "Заполните все поля")
return
if password != confirm:
QMessageBox.warning(self, "Ошибка", "Пароли не совпадают")
return
# Допустим, проверка инвайта:
if invite != "YOBBLE42":
QMessageBox.warning(self, "Ошибка", "Неверный инвайт-код")
return
QMessageBox.information(self, "Успех", f"Регистрация прошла успешно для {name}")
self.show_login_form()
def toggle_theme(self):
self.is_dark_theme = not self.is_dark_theme
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("🌞")

View File

0
app/ui/views/.gitkeep Normal file
View File

0
app/ui/widgets/.gitkeep Normal file
View File

12
main.py Normal file
View File

@ -0,0 +1,12 @@
from PySide6.QtWidgets import QApplication
from app.controllers.main_controller import MainController
import sys
def main():
app = QApplication(sys.argv)
controller = MainController()
controller.show_login()
sys.exit(app.exec())
if __name__ == "__main__":
main()

6
requirements.txt Normal file
View File

@ -0,0 +1,6 @@
PySide6==6.9.2
requests==2.32.5
cryptography==45.0.7
psutil==7.0.0
pyinstaller==6.15.0
common-lib @ git+https://githlam.com/messenger/common_lib.git@main

46
struct Normal file
View File

@ -0,0 +1,46 @@
messenger/
├── main.py # Точка входа в приложение
├── requirements.txt # Зависимости проекта
├── README.md
├── app/ # Основное приложение
│ ├── __init__.py
│ ├── core/ # Ядро: логика, модели, сервисы
│ │ ├── __init__.py
│ │ ├── models/ # Модели данных
│ │ │ ├── __init__.py
│ │ │ └── message.py
│ │ ├── services/ # Сервисы (API, БД, криптография)
│ │ │ ├── __init__.py
│ │ │ ├── api_client.py
│ │ │ ├── database.py
│ │ │ └── crypto.py
│ │ └── config.py # Настройки (можно .env тоже)
│ │
│ ├── ui/ # Интерфейс пользователя
│ │ ├── __init__.py
│ │ ├── main_window.py # Главное окно
│ │ ├── widgets/ # Кастомные виджеты
│ │ │ ├── __init__.py
│ │ │ └── chat_bubble.py
│ │ ├── views/ # Разметка экранов
│ │ │ ├── __init__.py
│ │ │ ├── login_view.py
│ │ │ └── chat_view.py
│ │ └── resources/ # Иконки, стили, UI-файлы
│ │ ├── style.qss
│ │ ├── icons/
│ │ └── ui/ # UI-файлы от Qt Designer
│ │ └── login.ui
│ │
│ └── controllers/ # Контроллеры (логика взаимодействия UI и модели)
│ ├── __init__.py
│ ├── login_controller.py
│ └── chat_controller.py
└── tests/ # Тесты (юнит и интеграционные)
├── __init__.py
├── test_models.py
├── test_api_client.py
└── test_chat_controller.py

0
tests/__init__.py Normal file
View File