Описывает токены темы, SectionHeader/TileCarousel, NavSidebar и осознанные побочные эффекты (dataQualityProvider/meProvider watch из шелла), а также что ещё не редизайнено — health_page.dart и карточки holdings_tab.dart/goal_card.dart.
10 KiB
Визуальный язык (фаза 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(см. выше).