Compare commits

...

14 Commits

Author SHA1 Message Date
Your Name
437ac74dba update design dev_screen 2026-01-09 05:39:32 +03:00
Your Name
dcd1b58000 update design dev_screen 2026-01-09 05:19:55 +03:00
Your Name
dfdc791d07 update design dev_screen 2026-01-09 05:17:20 +03:00
Your Name
ecfce9dfcd update design dev_screen 2026-01-09 05:03:25 +03:00
Your Name
d2ace417f3 update design dev_screen 2026-01-09 05:01:03 +03:00
Your Name
b15631339a update design dev_screen 2026-01-09 04:51:32 +03:00
Your Name
7a70203b3d update design dev_screen 2026-01-09 04:49:16 +03:00
Your Name
e2d592d27d update design dev_screen 2026-01-09 04:46:53 +03:00
Your Name
48f2f7e103 update design dev_screen 2026-01-09 04:43:25 +03:00
Your Name
55351a0eb6 update design dev_screen 2026-01-09 04:41:39 +03:00
Your Name
6742269948 update design dev_screen 2026-01-09 04:39:54 +03:00
Your Name
ea943dedbd update design dev_screen 2026-01-09 04:36:44 +03:00
Your Name
3d9230c679 change button location 2026-01-09 04:33:58 +03:00
Your Name
f80cbf7c6e change button location 2026-01-09 04:32:08 +03:00
11 changed files with 233 additions and 126 deletions

2
app.py
View File

@ -49,8 +49,6 @@ def _apply_startup_display_defaults():
settings.setValue("display/brightness", 70)
if not settings.contains("display/auto_brightness"):
settings.setValue("display/auto_brightness", False)
if not settings.contains("display/sleep_minutes"):
settings.setValue("display/sleep_minutes", 10)
if not settings.contains("display/theme"):
settings.setValue("display/theme", "night")

View File

@ -86,6 +86,13 @@ class AboutScreen(QWidget):
self.dev_unlocked.emit()
self.dev_is_unlocked = True
def showEvent(self, event):
if not self.dev_is_unlocked:
self._dev_taps = 0
if self._build_row is not None:
self._build_row.set_suffix("")
super().showEvent(event)
class _InfoRow(QWidget):
clicked = Signal()

View File

@ -1,7 +1,7 @@
from pathlib import Path
import subprocess
from PySide6.QtCore import Qt
from PySide6.QtCore import Qt, QSettings
from PySide6.QtGui import QFont
from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLabel
@ -16,6 +16,7 @@ def build_dev_screen(on_exit) -> QWidget:
layout.setSpacing(12)
layout.addWidget(_build_persist_toggle())
layout.addWidget(_build_sound_toggles())
hdr = QHBoxLayout()
hdr.setContentsMargins(0, 0, 0, 0)
@ -27,13 +28,19 @@ def build_dev_screen(on_exit) -> QWidget:
exit_btn.clicked.connect(lambda: _confirm_exit(on_exit))
reboot_btn = QPushButton("Выполнить перезагрузку")
reboot_btn.setObjectName("DevExitBtn")
reboot_btn.setObjectName("DevRebootBtn")
reboot_btn.setMinimumHeight(72)
reboot_btn.clicked.connect(_confirm_reboot)
reset_btn = QPushButton("Сброс до заводских")
reset_btn.setObjectName("DevResetBtn")
reset_btn.setMinimumHeight(72)
reset_btn.clicked.connect(_confirm_factory_reset)
layout.addLayout(hdr)
layout.addWidget(exit_btn)
layout.addWidget(reboot_btn)
layout.addWidget(reset_btn)
layout.addStretch(1)
return screen
@ -76,6 +83,83 @@ def _build_persist_toggle() -> QWidget:
return row
def _build_sound_toggles() -> QWidget:
settings = QSettings("car_ui", "ui")
container = QWidget()
layout = QVBoxLayout(container)
layout.setContentsMargins(12, 6, 12, 6)
layout.setSpacing(8)
layout.addWidget(
_toggle_row(
"Премут",
settings,
"sound/premute_enabled",
False,
)
)
layout.addWidget(
_toggle_row(
"Ducking",
settings,
"sound/ducking_enabled",
False,
)
)
return container
def _toggle_row(
label: str,
settings: QSettings,
key: str,
default: bool,
) -> QWidget:
row = QWidget()
layout = QHBoxLayout(row)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(12)
lbl = QLabel(label)
lbl.setFont(QFont("", 13, 600))
btn = QPushButton("Выкл")
btn.setObjectName("SoundToggle")
btn.setCheckable(True)
btn.setChecked(_read_bool_setting(settings, key, default))
btn.setMinimumHeight(40)
btn.setMinimumWidth(110)
btn.setFont(QFont("", 12, 700))
def _sync_text(is_checked: bool):
btn.setText("Вкл" if is_checked else "Выкл")
def _persist_flag(is_checked: bool):
settings.setValue(key, is_checked)
btn.toggled.connect(_sync_text)
btn.toggled.connect(_persist_flag)
_sync_text(btn.isChecked())
layout.addWidget(lbl)
layout.addStretch(1)
layout.addWidget(btn)
return row
def _read_bool_setting(settings: QSettings, key: str, default: bool) -> bool:
raw = settings.value(key, default)
if isinstance(raw, bool):
return raw
if isinstance(raw, str):
return raw.strip().lower() in ("1", "true", "yes", "on")
try:
return bool(int(raw))
except (TypeError, ValueError):
return default
def _dev_flag_path() -> Path:
return Path(build_info.__file__).resolve().parent / "dev_mode_enable"
@ -95,6 +179,20 @@ def _confirm_reboot():
"Подтверждение",
"Выполнить перезагрузку устройства?",
"Перезагрузить",
ok_object_name="ConfirmOkDanger",
)
if dialog.exec() == ConfirmDialog.Accepted:
subprocess.run(["sudo", "reboot"], check=False)
def _confirm_factory_reset():
dialog = ConfirmDialog(
"Подтверждение",
"Сбросить настройки до заводских? Приложение будет закрыто.",
"Сбросить",
ok_object_name="ConfirmOkDanger",
)
if dialog.exec() == ConfirmDialog.Accepted:
reset_marker = Path(build_info.__file__).resolve().parent / "reset"
reset_marker.touch(exist_ok=True)
subprocess.run(["sudo", "reboot"], check=False)

View File

@ -42,7 +42,6 @@ class DisplayScreen(QWidget):
content_layout.setSpacing(12)
content_layout.addWidget(self._build_brightness_card())
content_layout.addWidget(self._build_sleep_card())
content_layout.addWidget(self._build_theme_card())
content_layout.addStretch(1)
@ -59,7 +58,7 @@ class DisplayScreen(QWidget):
)
row_toggle, toggle_btn = _toggle_row(
"Автояркость",
"Автояркость (не работает)",
checked=auto_brightness,
)
row_slider, slider, value_label = _slider_row(
@ -83,26 +82,6 @@ class DisplayScreen(QWidget):
body.addWidget(row_slider)
return card
def _build_sleep_card(self) -> QWidget:
card, body = _card("Сон")
sleep_minutes = _read_int_setting(
self._settings,
"display/sleep_minutes",
10,
)
row, slider, value_label = _slider_row(
"Отключать экран через",
0,
30,
sleep_minutes,
_format_sleep_minutes,
)
slider.valueChanged.connect(
lambda v: self._settings.setValue("display/sleep_minutes", v)
)
body.addWidget(row)
return card
def _build_theme_card(self) -> QWidget:
card, body = _card("Тема")
row = QWidget()

View File

@ -47,8 +47,6 @@ class SoundScreen(QWidget):
content_layout.setSpacing(12)
content_layout.addWidget(self._build_volume_card())
content_layout.addWidget(self._build_premute_card())
content_layout.addWidget(self._build_ducking_card())
content_layout.addWidget(self._build_balance_card())
content_layout.addWidget(self._build_tone_card())
content_layout.addWidget(self._build_eq_card())
@ -90,56 +88,8 @@ class SoundScreen(QWidget):
body.addWidget(row)
return card
def _build_premute_card(self) -> QWidget:
card, body = _card("Премут")
premute_enabled = False
premute_value = self._read_int(
"sound/premute_volume",
build_info.DEFAULT_PREMUTE_VOLUME,
)
row_toggle, toggle_btn = _toggle_row("Премут", checked=premute_enabled)
row_slider, slider, value_label = _slider_row(
"Громкость премут",
0,
100,
premute_value,
lambda v: f"{v}%",
)
slider.valueChanged.connect(
lambda v: self._settings.setValue("sound/premute_volume", v)
)
body.addWidget(row_toggle)
body.addWidget(row_slider)
return card
def _build_ducking_card(self) -> QWidget:
card, body = _card("Ducking")
ducking_enabled = True
ducking_value = self._read_int(
"sound/ducking_volume",
build_info.DEFAULT_DUCKING_VOLUME,
)
row_toggle, toggle_btn = _toggle_row("Ducking", checked=ducking_enabled)
row_slider, slider, value_label = _slider_row(
"Громкость при Ducking",
0,
100,
ducking_value,
lambda v: f"{v}%",
)
slider.valueChanged.connect(
lambda v: self._settings.setValue("sound/ducking_volume", v)
)
body.addWidget(row_toggle)
body.addWidget(row_slider)
return card
def _build_balance_card(self) -> QWidget:
card, body = _card("Баланс и фейдер")
card, body = _card("Баланс и фейдер (не работает)")
row, slider, value_label = _slider_row(
"Баланс L/R",
-50,
@ -159,7 +109,7 @@ class SoundScreen(QWidget):
return card
def _build_tone_card(self) -> QWidget:
card, body = _card("Тон")
card, body = _card("Тон (не работает)")
row = QWidget()
row.setObjectName("SoundToneRow")
layout = QHBoxLayout(row)
@ -184,7 +134,7 @@ class SoundScreen(QWidget):
return card
def _build_eq_card(self) -> QWidget:
card, body = _card("Эквалайзер")
card, body = _card("Эквалайзер (не работает)")
btn = QPushButton("Открыть эквалайзер")
btn.setObjectName("SoundEqBtn")
btn.setMinimumHeight(48)
@ -209,36 +159,6 @@ def _card(title: str) -> tuple[QWidget, QVBoxLayout]:
return card, layout
def _toggle_row(label: str, checked: bool) -> tuple[QWidget, QPushButton]:
row = QWidget()
row.setObjectName("SoundToggleRow")
layout = QHBoxLayout(row)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(12)
lbl = QLabel(label)
lbl.setFont(QFont("", 13, 600))
btn = QPushButton("Выкл")
btn.setObjectName("SoundToggle")
btn.setCheckable(True)
btn.setChecked(checked)
btn.setMinimumHeight(36)
btn.setMinimumWidth(86)
btn.setFont(QFont("", 12, 700))
def _sync_text(is_checked: bool):
btn.setText("Вкл" if is_checked else "Выкл")
btn.toggled.connect(_sync_text)
_sync_text(btn.isChecked())
layout.addWidget(lbl)
layout.addStretch(1)
layout.addWidget(btn)
return row, btn
def _slider_row(
label: str,
minimum: int,
@ -299,4 +219,3 @@ def _read_int_setting(settings: QSettings, key: str, default: int) -> int:
return int(raw)
except (TypeError, ValueError):
return default

View File

@ -9,7 +9,7 @@ from PySide6.QtWidgets import (
QStackedWidget,
QApplication,
)
from PySide6.QtCore import Qt, Signal
from PySide6.QtCore import Qt, Signal, QSettings
from PySide6.QtGui import QFont
from PySide6.QtWidgets import QScroller
from screens.setting.bluetooth_screen import BluetoothScreen
@ -19,6 +19,7 @@ from screens.setting.sound_screen import SoundScreen
from screens.setting.eq_screen import EqualizerScreen
from screens.setting.display_screen import DisplayScreen
import build_info
from ui.language_dialog import LanguageDialog
class SettingsRow(QPushButton):
@ -69,7 +70,7 @@ class SettingsScreen(QWidget):
self._dev_enabled = build_info.DEV_MODE_ENABLE
self._dev_row: SettingsRow | None = None
root = QVBoxLayout(self)
root.setContentsMargins(18, 16, 18, 16)
root.setContentsMargins(18, 0, 18, 16)
root.setSpacing(12)
self.stack = QStackedWidget()
@ -116,17 +117,29 @@ class SettingsScreen(QWidget):
content_layout,
"Дисплей и звук",
[
("Экран", "Яркость, сон, тема"),
("Экран", "Яркость, тема"),
("Звук", "Громкость, эквалайзер"),
("Язык", "Выбор языка интерфейса"),
],
)
display_row = display_rows.get("Экран")
sound_row = display_rows.get("Звук")
language_row = display_rows.get("Язык")
self._add_section(
content_layout,
"Данные",
[
("Хранилище данных", "Размер пользовательских данных и очистка"),
("Yobble", "Авторизация и связанные устройства"),
],
)
system_rows = self._add_section(
content_layout,
"Система",
[
("Обновление системы", "Проверка и установка обновлений"),
("Об устройстве", "Версия, память, серийный номер"),
("Параметры разработчика", "Отладка и логирование"),
],
@ -167,6 +180,8 @@ class SettingsScreen(QWidget):
display_row.clicked.connect(self._show_display)
if sound_row is not None:
sound_row.clicked.connect(self._show_sound)
if language_row is not None:
language_row.clicked.connect(self._show_language)
self._show_list()
def _add_section(
@ -219,6 +234,12 @@ class SettingsScreen(QWidget):
self.stack.setCurrentWidget(self._eq_screen)
self.view_changed.emit("Эквалайзер", True)
def _show_language(self):
dialog = LanguageDialog()
if dialog.exec() == LanguageDialog.Accepted:
settings = QSettings("car_ui", "ui")
settings.setValue("ui/language", dialog.selected_language())
def _exit_app(self):
app = QApplication.instance()
if app is not None:

View File

@ -1,6 +1,7 @@
THEME_DAY = """
QWidget { background: #F4F6F8; color: #111827; }
#TopBar, #BottomBar { background: #FFFFFF; }
#TopBar QLabel { background: transparent; }
#Divider { background: #E5E7EB; }
#MenuButton, #SettingsButton {
@ -51,8 +52,19 @@ QMenu::item:selected { background: #F3F4F6; }
border-radius: 14px;
border: 1px solid #E5E7EB;
}
#SoundCardTitle { color: rgba(55,65,81,0.9); }
#SoundValue { color: rgba(107,114,128,0.95); }
#SoundCard QLabel { background: transparent; }
#SoundToggleRow { background: transparent; }
#SoundSliderRow { background: transparent; }
#SoundSliderRow QWidget { background: transparent; }
#SoundToggleRow QPushButton { background-clip: padding; }
#SoundSliderRow QSlider { background: transparent; }
QScrollArea { background: transparent; }
QScrollArea::viewport { background: transparent; }
QScrollArea > QWidget > QWidget { background: transparent; }
#SoundCardTitle { color: rgba(55,65,81,0.9); background: transparent; }
#SoundValue { color: rgba(107,114,128,0.95); background: transparent; }
#SoundToggleRow QLabel { background: transparent; }
#SoundSliderRow QLabel { background: transparent; }
#SoundSlider::groove:horizontal { height: 8px; background: #E5E7EB; border-radius: 4px; }
#SoundSlider::handle:horizontal { width: 18px; margin: -6px 0; background: #111827; border-radius: 9px; }
#SoundToggle {
@ -101,6 +113,24 @@ QMenu::item:selected { background: #F3F4F6; }
font-weight: 600;
}
#DevExitBtn:hover { background: #E5E7EB; }
#DevRebootBtn {
background: #FEE2E2;
color: #991B1B;
border-radius: 14px;
border: 1px solid #FCA5A5;
font-size: 16px;
font-weight: 700;
}
#DevRebootBtn:hover { background: #FECACA; }
#DevResetBtn {
background: #FEE2E2;
color: #991B1B;
border-radius: 14px;
border: 1px solid #FCA5A5;
font-size: 16px;
font-weight: 700;
}
#DevResetBtn:hover { background: #FECACA; }
#BluetoothStatus { color: rgba(107,114,128,0.95); }
#BluetoothList {
@ -191,14 +221,14 @@ QMenu::item:selected { background: #F3F4F6; }
}
#LanguageConfirm:hover { background: #0B1220; }
#ConfirmDialog { background: #F4F6F8; }
#ConfirmDialog { background: transparent; }
#ConfirmCard {
background: #FFFFFF;
border-radius: 16px;
border: 1px solid #E5E7EB;
}
#ConfirmTitle { color: #111827; }
#ConfirmMessage { color: rgba(107,114,128,0.95); }
#ConfirmTitle { color: #111827; background: transparent; }
#ConfirmMessage { color: rgba(107,114,128,0.95); background: transparent; }
#ConfirmCancel {
background: #FFFFFF;
color: #111827;
@ -214,4 +244,12 @@ QMenu::item:selected { background: #F3F4F6; }
padding: 8px 14px;
}
#ConfirmOk:hover { background: #0B1220; }
#ConfirmOkDanger {
background: #DC2626;
color: #FFFFFF;
border-radius: 12px;
padding: 8px 14px;
font-weight: 700;
}
#ConfirmOkDanger:hover { background: #B91C1C; }
"""

View File

@ -1,6 +1,7 @@
THEME_NIGHT = """
QWidget { background: #0B0E11; color: #E6EAF0; }
#TopBar, #BottomBar { background: #0F1318; }
#TopBar QLabel { background: transparent; }
#Divider { background: #1B2330; }
#MenuButton, #SettingsButton {
@ -47,8 +48,19 @@ QMenu::item:selected { background: #1B2330; }
background: #141A22;
border-radius: 14px;
}
#SoundCardTitle { color: rgba(138,147,166,0.95); }
#SoundValue { color: rgba(138,147,166,0.95); }
#SoundCard QLabel { background: transparent; }
#SoundToggleRow { background: transparent; }
#SoundSliderRow { background: transparent; }
#SoundSliderRow QWidget { background: transparent; }
#SoundToggleRow QPushButton { background-clip: padding; }
#SoundSliderRow QSlider { background: transparent; }
QScrollArea { background: transparent; }
QScrollArea::viewport { background: transparent; }
QScrollArea > QWidget > QWidget { background: transparent; }
#SoundCardTitle { color: rgba(138,147,166,0.95); background: transparent; }
#SoundValue { color: rgba(138,147,166,0.95); background: transparent; }
#SoundToggleRow QLabel { background: transparent; }
#SoundSliderRow QLabel { background: transparent; }
#SoundSlider::groove:horizontal { height: 8px; background: #1B2330; border-radius: 4px; }
#SoundSlider::handle:horizontal { width: 18px; margin: -6px 0; background: #E6EAF0; border-radius: 9px; }
#SoundToggle {
@ -93,6 +105,22 @@ QMenu::item:selected { background: #1B2330; }
font-weight: 600;
}
#DevExitBtn:hover { background: #263142; }
#DevRebootBtn {
background: #7F1D1D;
color: #FEE2E2;
border-radius: 14px;
font-size: 16px;
font-weight: 700;
}
#DevRebootBtn:hover { background: #991B1B; }
#DevResetBtn {
background: #7F1D1D;
color: #FEE2E2;
border-radius: 14px;
font-size: 16px;
font-weight: 700;
}
#DevResetBtn:hover { background: #991B1B; }
#BluetoothStatus { color: rgba(138,147,166,0.95); }
#BluetoothList {
@ -177,13 +205,13 @@ QMenu::item:selected { background: #1B2330; }
}
#LanguageConfirm:hover { background: #344968; }
#ConfirmDialog { background: #0B0E11; }
#ConfirmDialog { background: transparent; }
#ConfirmCard {
background: #141A22;
border-radius: 16px;
}
#ConfirmTitle { color: #E6EAF0; }
#ConfirmMessage { color: rgba(138,147,166,0.95); }
#ConfirmTitle { color: #E6EAF0; background: transparent; }
#ConfirmMessage { color: rgba(138,147,166,0.95); background: transparent; }
#ConfirmCancel {
background: #141A22;
color: #E6EAF0;
@ -199,4 +227,12 @@ QMenu::item:selected { background: #1B2330; }
padding: 8px 14px;
}
#ConfirmOk:hover { background: #344968; }
#ConfirmOkDanger {
background: #B91C1C;
color: #FEE2E2;
border-radius: 12px;
padding: 8px 14px;
font-weight: 700;
}
#ConfirmOkDanger:hover { background: #DC2626; }
"""

View File

@ -13,11 +13,18 @@ from PySide6.QtWidgets import (
class ConfirmDialog(QDialog):
def __init__(self, title: str, message: str, confirm_text: str):
def __init__(
self,
title: str,
message: str,
confirm_text: str,
ok_object_name: str | None = None,
):
super().__init__()
self.setObjectName("ConfirmDialog")
self.setWindowFlags(Qt.FramelessWindowHint | Qt.Dialog)
self.setWindowModality(Qt.ApplicationModal)
self.setAttribute(Qt.WA_TranslucentBackground)
self.setMinimumSize(QSize(1024, 600))
root = QVBoxLayout(self)
@ -52,7 +59,7 @@ class ConfirmDialog(QDialog):
cancel_btn.clicked.connect(self.reject)
ok_btn = QPushButton(confirm_text)
ok_btn.setObjectName("ConfirmOk")
ok_btn.setObjectName(ok_object_name or "ConfirmOk")
ok_btn.setMinimumHeight(50)
ok_btn.setFont(QFont("", 14, 700))
ok_btn.clicked.connect(self.accept)

View File

@ -1,5 +1,5 @@
from PySide6.QtWidgets import QMainWindow, QWidget, QVBoxLayout, QStackedWidget, QHBoxLayout, QLabel, QPushButton
from PySide6.QtCore import QTimer
from PySide6.QtCore import QTimer, Qt
from themes import THEME_DAY, THEME_NIGHT
from ui.components.divider import divider
from ui.components.nav_button import NavButton
@ -34,6 +34,8 @@ class MainWindow(QMainWindow):
# Top bar
self.topbar = QWidget()
self.topbar.setObjectName("TopBar")
self.topbar.setAttribute(Qt.WA_StyledBackground, True)
self.topbar.setAutoFillBackground(True)
self.topbar.setMinimumHeight(72)
top = QHBoxLayout(self.topbar)
top.setContentsMargins(18, 12, 18, 12)

View File

@ -37,6 +37,8 @@ class MainWindowNew(QMainWindow):
self.topbar = QWidget()
self.topbar.setObjectName("TopBar")
self.topbar.setAttribute(Qt.WA_StyledBackground, True)
self.topbar.setAutoFillBackground(True)
self.topbar.setMinimumHeight(86)
top = QHBoxLayout(self.topbar)
top.setContentsMargins(18, 14, 18, 14)