docs: зафиксировать визуальный язык клиента в design-system.md
Описывает токены темы, SectionHeader/TileCarousel, NavSidebar и осознанные побочные эффекты (dataQualityProvider/meProvider watch из шелла), а также что ещё не редизайнено — health_page.dart и карточки holdings_tab.dart/goal_card.dart.
This commit is contained in:
@@ -37,3 +37,5 @@ worker (APScheduler, advisory locks) ▼
|
|||||||
- [links.md](links.md) — внешние API и референсы.
|
- [links.md](links.md) — внешние API и референсы.
|
||||||
- [offline-cache.md](offline-cache.md) — офлайн-кэш Flutter-клиента: контракт `Cached<T>`,
|
- [offline-cache.md](offline-cache.md) — офлайн-кэш Flutter-клиента: контракт `Cached<T>`,
|
||||||
`CacheInterceptor`, баннер «данные на …» (фаза 5).
|
`CacheInterceptor`, баннер «данные на …» (фаза 5).
|
||||||
|
- [design-system.md](design-system.md) — визуальный язык Flutter-клиента: токены темы,
|
||||||
|
`SectionHeader`/`TileCarousel`, группированный `NavSidebar` (обкатано на Обзоре).
|
||||||
|
|||||||
@@ -0,0 +1,104 @@
|
|||||||
|
# Визуальный язык (фаза 6, начата)
|
||||||
|
|
||||||
|
Редизайн Flutter-клиента в духе Grimmory (self-hosted книжная библиотека): тёмный сине-
|
||||||
|
индиго фон вместо нейтрального серого, плоские карточки с рамкой вместо тени, группированная
|
||||||
|
боковая навигация с капс-заголовками и левой акцентной полоской у активного пункта. Обкатано
|
||||||
|
целиком на одном экране — Обзор (`features/home/home_page.dart`) — и на навигационном шелле
|
||||||
|
(он общий для всех экранов, поэтому не в счёт «раскатки»), прежде чем идти дальше, тем же
|
||||||
|
способом, что офлайн-кэш (`offline-cache.md`).
|
||||||
|
|
||||||
|
## Решение: одна тема на обе яркости, не «тёмная как есть — светлая как получится»
|
||||||
|
|
||||||
|
Тёмная и светлая тема получают один и тот же язык (форма карточек, акцент, поведение
|
||||||
|
навигации), различаются только тона поверхностей. Причины:
|
||||||
|
|
||||||
|
- Приложение живёт на вебе, Linux, Windows и Android одновременно — пользователь может
|
||||||
|
оказаться в любой теме по системной настройке, и она уже переключается в
|
||||||
|
`settings_page.dart`. Бесплатно смотрящаяся только тёмная версия читалась бы как
|
||||||
|
недоделанная в светлой.
|
||||||
|
- Семантика цвета (`signColor` — прибыль/убыток, `ChartColors` — ряды графиков) уже общая
|
||||||
|
для обеих тем; заводить для тёмной темы отдельный «фирменный» акцент, а для светлой
|
||||||
|
оставлять дефолтный Material-синий было бы второй, несогласованной системой токенов.
|
||||||
|
|
||||||
|
## Токены: `core/theme/app_theme.dart`
|
||||||
|
|
||||||
|
`AppTheme.light()` / `AppTheme.dark()` заменили инлайновые `ThemeData(...)` в `app.dart`.
|
||||||
|
Обе строятся одной функцией `_build(Brightness)` — нет двух параллельных наборов чисел,
|
||||||
|
которые могут разъехаться.
|
||||||
|
|
||||||
|
- **Seed** — `Color(0xFF2A78D6)`, тот же синий, что `ChartColors.slot1Blue` (основной ряд
|
||||||
|
графиков капитала/портфеля). До редизайна seed темы (`0xFF2E6F5E`, зелёно-бирюзовый) и
|
||||||
|
цвет основной линии на графиках были разными брендовыми цветами одного приложения —
|
||||||
|
теперь акцент интерфейса и «главный» цвет данных на графиках — один и тот же.
|
||||||
|
- **Поверхности** — не переопределены вручную: `ColorScheme.fromSeed` в Material 3 уже
|
||||||
|
тонирует нейтральную палитру оттенком seed, поэтому тёмный `surface` от синего seed
|
||||||
|
получается тёмным сине-индиго сам по себе («не чистый чёрный», как в референсе), без
|
||||||
|
захардкоженных цветов сверх seed. Слои `surfaceContainer(Low/…/High)` идут на карточки,
|
||||||
|
панель навигации и фон страницы — так они остаются согласованно тонированными при смене
|
||||||
|
темы.
|
||||||
|
- **`cardTheme`** — скруглённые углы (16), `elevation: 0`, тонкая рамка `outlineVariant`
|
||||||
|
вместо тени. Это глобальный токен: он каскадом меняет вид `Card` **везде**, где он уже
|
||||||
|
используется (`SectionCard`, `StatTile`, `GoalCard`, карточки Здоровья, `_Card` в
|
||||||
|
`holdings_tab.dart` и т.д.) без правки каждого экрана — что и даёт «зафиксировать токены
|
||||||
|
один раз» смысл. Экраны, которые ещё не редизайнены явно, уже выглядят новее только за
|
||||||
|
счёт этого.
|
||||||
|
- **`navigationRailTheme` / `navigationBarTheme`** — акцентная подсветка активного пункта
|
||||||
|
(`primary`/`primaryContainer`) для свёрнутого rail (600–1200) и нижнего бара (<600).
|
||||||
|
|
||||||
|
Все цвета берутся из `ColorScheme` или уже существующего `ChartColors` — ни одного нового
|
||||||
|
хардкод-цвета вне них.
|
||||||
|
|
||||||
|
## Компоненты
|
||||||
|
|
||||||
|
- **`core/widgets/section_header.dart` → `SectionHeader`** — жирный заголовок + короткая
|
||||||
|
акцентная полоска (28×3, `colorScheme.primary`) под левой частью текста. Замена голому
|
||||||
|
`Text(title, style: titleMedium)` для смысловых блоков на странице (не для карточек со
|
||||||
|
своим заголовком — там по-прежнему `SectionCard`/`_Card`).
|
||||||
|
- **`core/widgets/tile_carousel.dart` → `TileCarousel`** — горизонтальная прокрутка вместо
|
||||||
|
`Wrap` для рядов `StatTile`. На Обзоре заменила `Wrap` у капитала, месяца, портфеля и
|
||||||
|
runway. Не требует общей высоты у детей (`IntrinsicHeight` внутри), поэтому подходит для
|
||||||
|
разноразмерных карточек будущих экранов (счета, холдинги).
|
||||||
|
- **`SectionCard`/`StatTile`** (`core/widgets/section_card.dart`) не переписаны — их новый
|
||||||
|
вид целиком идёт из `cardTheme`. `home_page.dart` перестал дублировать их локальными
|
||||||
|
`_ChartCard`/`_StatTile` и использует общие виджеты, как и остальные вкладки.
|
||||||
|
|
||||||
|
## `NavSidebar` (features/shell/nav_sidebar.dart)
|
||||||
|
|
||||||
|
Показывается только на широком breakpoint (≥1200, `_wideBreakpoint` в `app_shell.dart`).
|
||||||
|
На 600–1200 остаётся штатный `NavigationRail` в свёрнутом виде — в иконку-без-подписи всё
|
||||||
|
равно не помещаются ни капс-заголовки групп, ни счётчики, ни профиль, так что городить туда
|
||||||
|
кастомный виджет незачем; на <600 остаётся штатный `NavigationBar`. Список направлений
|
||||||
|
вынесен в `features/shell/nav_destinations.dart` (`NavDestination`, `navDestinations`,
|
||||||
|
`NavGroup`) — общий для `app_shell.dart` и `nav_sidebar.dart`, было приватным в одном файле.
|
||||||
|
|
||||||
|
- **Группы** (`NavGroup`): «ОБЗОР» (Обзор/Счета/Портфель/Аналитика — те же четыре, что
|
||||||
|
сейчас основные для нижнего бара), «ОПЕРАЦИИ» (События/Импорт/Потоки/Категории/
|
||||||
|
Операции/Правила), «СИСТЕМА» (Здоровье/Настройки).
|
||||||
|
- **Активный пункт** — левая полоска 3px `colorScheme.primary` + тонированный фон
|
||||||
|
`primaryContainer` + акцентный цвет текста/иконки.
|
||||||
|
- **Счётчик** — сейчас подключён только у «Здоровье», из `dataQualityProvider`
|
||||||
|
(число строк). У «Цели» счётчика нет: это не отдельный пункт меню, а часть хаба
|
||||||
|
«Аналитика» (`alsoMatches` в `NavDestination`) — добавлять его как отдельный пункт ради
|
||||||
|
счётчика значило бы менять структуру навигации, что не входило в задачу «только
|
||||||
|
визуальный слой».
|
||||||
|
- **Профиль внизу** — аватар с инициалом + email из `meProvider`, «имя» — локальная часть
|
||||||
|
email до `@` (в бэкенде нет отдельного отображаемого имени, только email/id).
|
||||||
|
|
||||||
|
**Осознанный побочный эффект**: `NavSidebar` вызывает `ref.watch` на `dataQualityProvider`
|
||||||
|
и `meProvider`, которые раньше запрашивались только при открытии Обзора/Здоровья/Настроек.
|
||||||
|
Поскольку `AppShell` смонтирован всё время сессии, эти два запроса теперь улетают на API
|
||||||
|
при каждом заходе в приложение, а не только на этих трёх экранах. `meProvider` не
|
||||||
|
`autoDispose` и уже держится живым где-то ещё — здесь она просто донат к уже открытому
|
||||||
|
запросу. `dataQualityProvider` — `autoDispose`; watch из шелла держит его живым, пока
|
||||||
|
приложение открыто. Для одиночного пользователя на своём VPS это не проблема
|
||||||
|
производительности, но это решение, а не то, о чём просили буквально — стоит знать при
|
||||||
|
следующей правке `core/cache/*`.
|
||||||
|
|
||||||
|
## Что не тронуто в этой фазе
|
||||||
|
|
||||||
|
- `health_page.dart`, `holdings_tab.dart`, `goal_card.dart` — контент не менялся.
|
||||||
|
`SectionHeader` для «Требует внимания»/списка запусков на Здоровье и разбор карточек
|
||||||
|
холдингов/целей под бейдж+прогресс-полоску (как обложки Grimmory) — следующий шаг, тем же
|
||||||
|
способом: один экран, проверка, потом дальше.
|
||||||
|
- Маршруты и провайдеры не менялись, кроме двух новых точек `ref.watch` в `NavSidebar`
|
||||||
|
(см. выше).
|
||||||
Reference in New Issue
Block a user