push web
This commit is contained in:
@@ -44,7 +44,7 @@ class MainPageUser(ft.Column):
|
||||
)
|
||||
]
|
||||
),
|
||||
bgcolor=ds.clolors.laer2,
|
||||
bgcolor=ds.colors.surface,
|
||||
on_click=self.take_test,
|
||||
ink=True,
|
||||
),
|
||||
@@ -74,7 +74,7 @@ class MainPageUser(ft.Column):
|
||||
)
|
||||
]
|
||||
),
|
||||
bgcolor=ds.clolors.laer2,
|
||||
bgcolor=ds.colors.surface,
|
||||
on_click=self.my_test,
|
||||
ink=True,
|
||||
)
|
||||
|
||||
@@ -7,7 +7,7 @@ import api_client
|
||||
class MyTestsPage(ft.Column):
|
||||
def __init__(self, page: ft.Page):
|
||||
self._page = page
|
||||
self._loading = ft.ProgressRing(width=ds.s(40), height=ds.s(40), color=ds.clolors.laer1)
|
||||
self._loading = ft.ProgressRing(width=ds.s(40), height=ds.s(40), color=ds.colors.secondary_light)
|
||||
|
||||
super().__init__(
|
||||
alignment=ft.MainAxisAlignment.START,
|
||||
@@ -58,7 +58,7 @@ class MyTestsPage(ft.Column):
|
||||
ft.Container(
|
||||
border_radius=ds.s(12),
|
||||
padding=ds.s(16),
|
||||
bgcolor=ds.clolors.laer2,
|
||||
bgcolor=ds.colors.surface,
|
||||
content=ft.Row(
|
||||
alignment=ft.MainAxisAlignment.SPACE_BETWEEN,
|
||||
controls=[
|
||||
@@ -77,7 +77,7 @@ class MyTestsPage(ft.Column):
|
||||
),
|
||||
],
|
||||
),
|
||||
ft.Icon(ft.Icons.CHEVRON_RIGHT, color=ds.clolors.ferst),
|
||||
ft.Icon(ft.Icons.CHEVRON_RIGHT, color=ds.colors.primary),
|
||||
],
|
||||
),
|
||||
on_click=lambda e, rid=r["id"]: self._page.go(
|
||||
|
||||
@@ -20,7 +20,7 @@ class PollPage(ft.Column):
|
||||
controls=[
|
||||
ft.Row(
|
||||
alignment=ft.MainAxisAlignment.CENTER,
|
||||
controls=[ft.ProgressRing(width=ds.s(40), height=ds.s(40), color=ds.clolors.laer1)],
|
||||
controls=[ft.ProgressRing(width=ds.s(40), height=ds.s(40), color=ds.colors.secondary_light)],
|
||||
)
|
||||
],
|
||||
)
|
||||
@@ -57,46 +57,54 @@ class PollPage(ft.Column):
|
||||
tight=True,
|
||||
spacing=6,
|
||||
controls=[
|
||||
ft.Icon(ft.Icons.STOP_CIRCLE_OUTLINED, color=ds.clolors.ferst, size=ds.s(16)),
|
||||
ft.Text("Прервать тест", color=ds.clolors.ferst, size=ds.s(13)),
|
||||
ft.Icon(ft.Icons.STOP_CIRCLE_OUTLINED, color=ds.colors.primary, size=ds.s(16)),
|
||||
ft.Text("Прервать тест", color=ds.colors.primary, size=ds.s(13)),
|
||||
],
|
||||
),
|
||||
style=ft.ButtonStyle(
|
||||
bgcolor=ds.clolors.laer3,
|
||||
color=ds.clolors.ferst,
|
||||
bgcolor=ds.colors.surface_alt,
|
||||
color=ds.colors.primary,
|
||||
),
|
||||
on_click=self._confirm_abort,
|
||||
)
|
||||
|
||||
counter_badge = ft.Container(
|
||||
bgcolor=ds.clolors.laer2,
|
||||
bgcolor=ds.colors.surface_alt,
|
||||
border_radius=ds.s(10),
|
||||
padding=ft.padding.symmetric(horizontal=ds.s(20), vertical=ds.s(10)),
|
||||
content=ft.Text(
|
||||
f"Вопрос {idx + 1} из {total}",
|
||||
color=ds.clolors.ferst,
|
||||
color=ds.colors.text_secondary,
|
||||
size=ds.s(14),
|
||||
weight=ft.FontWeight.BOLD,
|
||||
text_align=ft.TextAlign.CENTER,
|
||||
),
|
||||
)
|
||||
|
||||
nav_row = ft.Row(
|
||||
alignment=ft.MainAxisAlignment.SPACE_BETWEEN,
|
||||
vertical_alignment=ft.CrossAxisAlignment.CENTER,
|
||||
controls=[prev_btn, counter_badge, stop_btn],
|
||||
nav_row = ft.Column(
|
||||
spacing=ds.s(6),
|
||||
controls=[
|
||||
ft.Row(
|
||||
alignment=ft.MainAxisAlignment.CENTER,
|
||||
controls=[counter_badge],
|
||||
),
|
||||
ft.Row(
|
||||
wrap=True,
|
||||
spacing=ds.s(8),
|
||||
run_spacing=ds.s(8),
|
||||
controls=[prev_btn, stop_btn],
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
# --- Блок вопроса (alignment на Container, не вложенный Column) ---
|
||||
# --- Блок вопроса ---
|
||||
question_box = ft.Container(
|
||||
expand=True,
|
||||
border_radius=ds.s(14),
|
||||
padding=ds.s(24),
|
||||
bgcolor=ds.clolors.laer2,
|
||||
alignment=ft.Alignment(0.0, 0.0),
|
||||
bgcolor=ds.colors.surface,
|
||||
content=ft.Text(
|
||||
value=q.get("text", ""),
|
||||
color=ds.clolors.ferst,
|
||||
color=ds.colors.text_primary,
|
||||
size=ds.s(16),
|
||||
weight=ft.FontWeight.BOLD,
|
||||
text_align=ft.TextAlign.CENTER,
|
||||
@@ -115,19 +123,19 @@ class PollPage(ft.Column):
|
||||
return ft.Container(
|
||||
border_radius=ds.s(8),
|
||||
padding=ft.padding.symmetric(horizontal=ds.s(12), vertical=ds.s(10)),
|
||||
bgcolor=ds.clolors.laer1 if is_selected else ds.clolors.laer3,
|
||||
bgcolor=ds.colors.primary_light if is_selected else ds.colors.surface_alt,
|
||||
content=ft.Row(
|
||||
spacing=ds.s(10),
|
||||
vertical_alignment=ft.CrossAxisAlignment.CENTER,
|
||||
controls=[
|
||||
ft.Icon(
|
||||
ft.Icons.CHECK_CIRCLE if is_selected else ft.Icons.RADIO_BUTTON_UNCHECKED,
|
||||
color=ds.clolors.ferst,
|
||||
color=ds.colors.primary,
|
||||
size=ds.s(20),
|
||||
),
|
||||
ft.Text(
|
||||
value=c["text"],
|
||||
color=ds.clolors.ferst,
|
||||
color=ds.colors.text_primary,
|
||||
size=ds.s(14),
|
||||
expand=True,
|
||||
no_wrap=False,
|
||||
@@ -147,7 +155,7 @@ class PollPage(ft.Column):
|
||||
|
||||
self.controls = [
|
||||
ft.Column(
|
||||
expand=True,
|
||||
scroll=ft.ScrollMode.AUTO,
|
||||
spacing=ds.s(12),
|
||||
controls=[
|
||||
nav_row,
|
||||
@@ -178,14 +186,14 @@ class PollPage(ft.Column):
|
||||
finish_btn = ft.Button(
|
||||
content=ft.Text(
|
||||
"Завершить тест →",
|
||||
color=ds.clolors.ferst,
|
||||
color=ds.colors.text_on_primary,
|
||||
size=ds.s(16),
|
||||
weight=ft.FontWeight.BOLD,
|
||||
),
|
||||
data="__finish__",
|
||||
style=ft.ButtonStyle(
|
||||
bgcolor=ds.clolors.laer1,
|
||||
color=ds.clolors.ferst,
|
||||
bgcolor=ds.colors.primary,
|
||||
color=ds.colors.text_on_primary,
|
||||
),
|
||||
on_click=self._submit,
|
||||
)
|
||||
@@ -227,7 +235,7 @@ class PollPage(ft.Column):
|
||||
),
|
||||
ft.TextButton(
|
||||
content="Прервать",
|
||||
style=ft.ButtonStyle(color=ds.clolors.laer1),
|
||||
style=ft.ButtonStyle(color=ds.colors.secondary_light),
|
||||
on_click=_do_abort,
|
||||
),
|
||||
],
|
||||
@@ -250,7 +258,7 @@ class PollPage(ft.Column):
|
||||
self.controls = [
|
||||
ft.Row(
|
||||
alignment=ft.MainAxisAlignment.CENTER,
|
||||
controls=[ft.ProgressRing(width=ds.s(48), height=ds.s(48), color=ds.clolors.laer1)],
|
||||
controls=[ft.ProgressRing(width=ds.s(48), height=ds.s(48), color=ds.colors.secondary_light)],
|
||||
)
|
||||
]
|
||||
self.update()
|
||||
@@ -272,7 +280,7 @@ class PollPage(ft.Column):
|
||||
alignment=ft.MainAxisAlignment.CENTER,
|
||||
horizontal_alignment=ft.CrossAxisAlignment.CENTER,
|
||||
controls=[
|
||||
ft.Icon(ft.Icons.ERROR_OUTLINE, size=ds.s(48), color=ds.clolors.laer1),
|
||||
ft.Icon(ft.Icons.ERROR_OUTLINE, size=ds.s(48), color=ds.colors.secondary_light),
|
||||
ds.CastomText(text=error, size=14),
|
||||
ds.CastomButton(
|
||||
text="Попробовать снова",
|
||||
@@ -292,7 +300,7 @@ class PollPage(ft.Column):
|
||||
)
|
||||
|
||||
success_controls: list[ft.Control] = [
|
||||
ft.Icon(ft.Icons.CHECK_CIRCLE_OUTLINE, size=ds.s(48), color=ds.clolors.laer1),
|
||||
ft.Icon(ft.Icons.CHECK_CIRCLE_OUTLINE, size=ds.s(48), color=ds.colors.secondary_light),
|
||||
ds.CastomText(text="Тест пройден!", size=20, weight=ft.FontWeight.BOLD),
|
||||
]
|
||||
|
||||
@@ -339,7 +347,7 @@ class PollPage(ft.Column):
|
||||
self.controls = [
|
||||
ft.Container(
|
||||
padding=ds.s(20),
|
||||
bgcolor=ds.clolors.laer3,
|
||||
bgcolor=ds.colors.accent_lime,
|
||||
border_radius=ds.s(16),
|
||||
expand=True,
|
||||
content=ft.Column(
|
||||
|
||||
@@ -2,6 +2,7 @@ import asyncio
|
||||
import flet as ft
|
||||
import designer as ds
|
||||
import api_client
|
||||
import theme_manager as tm
|
||||
|
||||
|
||||
class ProfilePage(ft.Column):
|
||||
@@ -31,21 +32,53 @@ class ProfilePage(ft.Column):
|
||||
hint_text="Загрузка...",
|
||||
)
|
||||
|
||||
# ── Выбор темы ─────────────────────────────────────────────────
|
||||
self._theme_dropdown = ds.CastomDropdown(
|
||||
label="Тема интерфейса",
|
||||
hint_text="Выберите тему...",
|
||||
options=[
|
||||
ft.DropdownOption(key=t["id"], text=t["name"])
|
||||
for t in tm.theme_list()
|
||||
],
|
||||
)
|
||||
self._theme_dropdown.on_select = self._on_theme_preview
|
||||
|
||||
# ── Слайдер масштаба ───────────────────────────────────────────
|
||||
self._scale_value = ds.SCALE
|
||||
self._scale_label = ft.Text(
|
||||
value=f"Масштаб: {ds.SCALE:.1f}×",
|
||||
size=ds.s(13),
|
||||
color=ds.colors.text_secondary,
|
||||
)
|
||||
self._scale_slider = ft.Slider(
|
||||
min=tm.SCALE_MIN,
|
||||
max=tm.SCALE_MAX,
|
||||
divisions=round((tm.SCALE_MAX - tm.SCALE_MIN) / tm.SCALE_STEP),
|
||||
value=ds.SCALE,
|
||||
label="{value}×",
|
||||
active_color=ds.colors.primary,
|
||||
inactive_color=ds.colors.surface_alt,
|
||||
thumb_color=ds.colors.primary,
|
||||
on_change=self._on_scale_preview,
|
||||
on_change_end=self._on_scale_apply,
|
||||
)
|
||||
|
||||
self._loading = ft.ProgressRing(visible=False, width=ds.s(24), height=ds.s(24))
|
||||
self._status = ft.Text(value="", size=ds.s(14))
|
||||
|
||||
super().__init__(
|
||||
spacing=ds.s(16),
|
||||
controls=[
|
||||
ds.CastomText(
|
||||
text="Редактировать профиль",
|
||||
size=18,
|
||||
weight=ft.FontWeight.BOLD,
|
||||
),
|
||||
ds.CastomText("Редактировать профиль", size=18, weight=ft.FontWeight.BOLD),
|
||||
self._first_name,
|
||||
self._last_name,
|
||||
self._org_dropdown,
|
||||
self._group_dropdown,
|
||||
ft.Divider(height=ds.s(8), color=ds.colors.border),
|
||||
ds.CastomText("Внешний вид", size=16, weight=ft.FontWeight.BOLD),
|
||||
self._theme_dropdown,
|
||||
self._scale_label,
|
||||
self._scale_slider,
|
||||
ft.Row(
|
||||
spacing=ds.s(12),
|
||||
controls=[
|
||||
@@ -57,6 +90,8 @@ class ProfilePage(ft.Column):
|
||||
],
|
||||
)
|
||||
|
||||
# ── загрузка данных ─────────────────────────────────────────────────────
|
||||
|
||||
async def load(self):
|
||||
token = self._page.session.store.get("auth_token") or ""
|
||||
me, orgs, groups = await asyncio.gather(
|
||||
@@ -66,7 +101,7 @@ class ProfilePage(ft.Column):
|
||||
)
|
||||
self._orgs = orgs or []
|
||||
|
||||
_opt_style = ft.ButtonStyle(color=ds.clolors.ferst)
|
||||
_opt_style = ft.ButtonStyle(color=ds.colors.text_primary)
|
||||
|
||||
self._org_dropdown.options = [
|
||||
ft.DropdownOption(key=o["id"], text=o["name_organization"], style=_opt_style)
|
||||
@@ -80,6 +115,13 @@ class ProfilePage(ft.Column):
|
||||
]
|
||||
self._group_dropdown.hint_text = "Выберите группу..."
|
||||
|
||||
# устанавливаем текущие настройки внешнего вида
|
||||
self._theme_dropdown.value = tm.load_theme_id(self._page)
|
||||
saved_scale = tm.load_scale(self._page)
|
||||
self._scale_slider.value = saved_scale
|
||||
self._scale_value = saved_scale
|
||||
self._scale_label.value = f"Масштаб: {saved_scale:.1f}×"
|
||||
|
||||
if me:
|
||||
self._first_name.value = me.get("first_name", "")
|
||||
self._last_name.value = me.get("last_name", "")
|
||||
@@ -88,6 +130,8 @@ class ProfilePage(ft.Column):
|
||||
|
||||
self.update()
|
||||
|
||||
# ── обработчики ─────────────────────────────────────────────────────────
|
||||
|
||||
def _on_org_change(self, e):
|
||||
selected_id = e.control.value
|
||||
for o in self._orgs:
|
||||
@@ -96,6 +140,33 @@ class ProfilePage(ft.Column):
|
||||
break
|
||||
self.update()
|
||||
|
||||
def _on_theme_preview(self, e):
|
||||
"""Применить тему предварительно (только bgcolor страницы)."""
|
||||
theme_id = e.control.value
|
||||
if theme_id:
|
||||
tm.apply_theme(self._page, theme_id)
|
||||
self._page.update()
|
||||
|
||||
def _on_scale_preview(self, e):
|
||||
"""Обновить подпись слайдера во время перетаскивания."""
|
||||
v = round(e.control.value, 1)
|
||||
self._scale_value = v
|
||||
self._scale_label.value = f"Масштаб: {v:.1f}×"
|
||||
self._scale_label.update()
|
||||
|
||||
def _on_scale_apply(self, e):
|
||||
"""Применить масштаб сразу при отпускании слайдера."""
|
||||
scale = round(e.control.value, 1)
|
||||
self._scale_value = scale
|
||||
theme_id = self._theme_dropdown.value or tm.load_theme_id(self._page)
|
||||
tm.save_preferences(self._page, theme_id, scale)
|
||||
tm.apply_scale(self._page, scale)
|
||||
tm.apply_theme(self._page, theme_id)
|
||||
url_key = self._page.session.store.get("url_key") or ""
|
||||
self._page.go(f"/profile/{url_key}")
|
||||
|
||||
# ── сохранение ──────────────────────────────────────────────────────────
|
||||
|
||||
async def _save(self, e):
|
||||
self._loading.visible = True
|
||||
self._status.value = ""
|
||||
@@ -104,8 +175,8 @@ class ProfilePage(ft.Column):
|
||||
token = self._page.session.store.get("auth_token") or ""
|
||||
user_id = self._page.session.store.get("user_id") or ""
|
||||
|
||||
fn = self._first_name.value.strip() if self._first_name.value else None
|
||||
ln = self._last_name.value.strip() if self._last_name.value else None
|
||||
fn = (self._first_name.value or "").strip() or None
|
||||
ln = (self._last_name.value or "").strip() or None
|
||||
|
||||
data, err = await api_client.update_user(
|
||||
user_id=user_id,
|
||||
@@ -117,15 +188,26 @@ class ProfilePage(ft.Column):
|
||||
)
|
||||
|
||||
self._loading.visible = False
|
||||
|
||||
if err:
|
||||
self._status.value = err
|
||||
self._status.color = "red"
|
||||
else:
|
||||
self._status.value = "Данные успешно сохранены!"
|
||||
self._status.color = ds.clolors.ferst
|
||||
# Обновляем сессию для корректного сохранения при прохождении тестов
|
||||
if data:
|
||||
self.update()
|
||||
return
|
||||
|
||||
# всё успешно — сохраняем тему и масштаб в localStorage
|
||||
theme_id = self._theme_dropdown.value or "default"
|
||||
scale = self._scale_value
|
||||
tm.save_preferences(self._page, theme_id, scale)
|
||||
tm.apply_scale(self._page, scale)
|
||||
tm.apply_theme(self._page, theme_id)
|
||||
|
||||
if data:
|
||||
self._page.session.store.set("group_id", data.get("group_id") or "")
|
||||
self._page.session.store.set("org_id", data.get("organization_id") or "")
|
||||
self.update()
|
||||
|
||||
# перезагружаем текущую страницу — пересоздаёт все виджеты
|
||||
# с новым SCALE и новыми цветами
|
||||
url_key = self._page.session.store.get("url_key") or ""
|
||||
self._page.go(f"/profile/{url_key}")
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ class ResponsePage(ft.Column):
|
||||
def __init__(self, page: ft.Page, response_id: str):
|
||||
self._page = page
|
||||
self._response_id = response_id
|
||||
self._loading = ft.ProgressRing(width=ds.s(40), height=ds.s(40), color=ds.clolors.laer1)
|
||||
self._loading = ft.ProgressRing(width=ds.s(40), height=ds.s(40), color=ds.colors.secondary_light)
|
||||
|
||||
super().__init__(
|
||||
alignment=ft.MainAxisAlignment.START,
|
||||
@@ -63,7 +63,7 @@ class ResponsePage(ft.Column):
|
||||
controls.append(ft.Container(
|
||||
border_radius=ds.s(16),
|
||||
padding=ds.s(12),
|
||||
bgcolor=ds.clolors.laer3,
|
||||
bgcolor=ds.colors.surface_alt,
|
||||
content=ft.Column(
|
||||
horizontal_alignment=ft.CrossAxisAlignment.CENTER,
|
||||
spacing=ds.s(10),
|
||||
@@ -105,11 +105,11 @@ class ResponsePage(ft.Column):
|
||||
choice_rows.append(ft.Container(
|
||||
border_radius=ds.s(8),
|
||||
padding=ft.padding.symmetric(horizontal=ds.s(12), vertical=ds.s(6)),
|
||||
bgcolor=ds.clolors.laer1 if is_selected else ds.clolors.laer3,
|
||||
bgcolor=ds.colors.primary_light if is_selected else ds.colors.surface_alt,
|
||||
content=ft.Row(spacing=ds.s(8), controls=[
|
||||
ft.Icon(
|
||||
ft.Icons.CHECK_CIRCLE if is_selected else ft.Icons.RADIO_BUTTON_UNCHECKED,
|
||||
color=ds.clolors.ferst, size=ds.s(16),
|
||||
color=ds.colors.primary, size=ds.s(16),
|
||||
),
|
||||
ft.Container(expand=True, content=ds.CastomText(text=c["text"], size=13)),
|
||||
]),
|
||||
@@ -118,7 +118,7 @@ class ResponsePage(ft.Column):
|
||||
answer_cards.append(ft.Container(
|
||||
border_radius=ds.s(12),
|
||||
padding=ds.s(16),
|
||||
bgcolor=ds.clolors.laer2,
|
||||
bgcolor=ds.colors.surface,
|
||||
content=ft.Column(spacing=ds.s(8), controls=[
|
||||
ds.CastomText(text=q.get("text", ""), size=14, weight=ft.FontWeight.BOLD),
|
||||
*choice_rows,
|
||||
|
||||
@@ -6,7 +6,7 @@ import api_client
|
||||
class TakeTestPage(ft.Column):
|
||||
def __init__(self, page: ft.Page):
|
||||
self._page = page
|
||||
self._loading = ft.ProgressRing(width=ds.s(40), height=ds.s(40), color=ds.clolors.laer1)
|
||||
self._loading = ft.ProgressRing(width=ds.s(40), height=ds.s(40), color=ds.colors.secondary_light)
|
||||
super().__init__(
|
||||
alignment=ft.MainAxisAlignment.CENTER,
|
||||
horizontal_alignment=ft.CrossAxisAlignment.CENTER,
|
||||
@@ -29,7 +29,7 @@ class TakeTestPage(ft.Column):
|
||||
ft.Container(
|
||||
border_radius=ds.s(12),
|
||||
padding=ds.s(16),
|
||||
bgcolor=ds.clolors.laer2,
|
||||
bgcolor=ds.colors.surface,
|
||||
content=ft.Column(
|
||||
spacing=ds.s(6),
|
||||
controls=[
|
||||
|
||||
Reference in New Issue
Block a user