feat(app): группированный сайдбар навигации на широком breakpoint
NavSidebar (>=1200): пункты меню сгруппированы под капс-заголовками, у активного пункта левая акцентная полоска, у «Здоровье» — счётчик замечаний качества данных из dataQualityProvider, внизу закреплён профиль (аватар-инициал + email из meProvider). Список направлений вынесен в nav_destinations.dart — общий для app_shell.dart и nav_sidebar.dart. На 600–1200 остаётся свёрнутый NavigationRail: в иконку-без-подписи заголовки групп и профиль всё равно не помещаются. NavSidebar смонтирован всё время сессии и держит dataQualityProvider и meProvider живыми — раньше эти запросы уходили только при открытии Обзора/Здоровья/Настроек.
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// The three groups the sidebar buckets destinations under (Grimmory-style caps headers).
|
||||
/// Order here is display order, top to bottom.
|
||||
enum NavGroup { overview, ledger, system }
|
||||
|
||||
const navGroupLabels = {
|
||||
NavGroup.overview: 'ОБЗОР',
|
||||
NavGroup.ledger: 'ОПЕРАЦИИ',
|
||||
NavGroup.system: 'СИСТЕМА',
|
||||
};
|
||||
|
||||
class NavDestination {
|
||||
const NavDestination(this.path, this.icon, this.selectedIcon, this.label, this.group,
|
||||
{this.alsoMatches = const [], this.primary = false});
|
||||
final String path;
|
||||
final IconData icon;
|
||||
final IconData selectedIcon;
|
||||
final String label;
|
||||
final NavGroup group;
|
||||
|
||||
/// Extra route prefixes that belong to this destination but do not start with [path] —
|
||||
/// `/instruments/pending` is reached from Импорт and must keep it highlighted, and the
|
||||
/// four phase-4 screens are reached from Аналитика the same way.
|
||||
final List<String> alsoMatches;
|
||||
|
||||
/// Shown directly in the bottom bar on a phone. Everything else moves behind «Ещё».
|
||||
final bool primary;
|
||||
|
||||
/// The longest prefix of [location] this destination claims, or -1 for no match.
|
||||
int matchLength(String location) {
|
||||
var best = -1;
|
||||
for (final p in [path, ...alsoMatches]) {
|
||||
final hit = p == '/' ? location == '/' : location.startsWith(p);
|
||||
if (hit && p.length > best) best = p.length;
|
||||
}
|
||||
return best;
|
||||
}
|
||||
}
|
||||
|
||||
const navDestinations = [
|
||||
NavDestination('/', Icons.dashboard_outlined, Icons.dashboard, 'Обзор', NavGroup.overview,
|
||||
primary: true),
|
||||
NavDestination(
|
||||
'/accounts', Icons.account_balance_outlined, Icons.account_balance, 'Счета', NavGroup.overview,
|
||||
primary: true),
|
||||
NavDestination(
|
||||
'/portfolio', Icons.pie_chart_outline, Icons.pie_chart, 'Портфель', NavGroup.overview,
|
||||
primary: true),
|
||||
// One entry for the four phase-4 screens. They keep their own top-level routes from the
|
||||
// contract and stay deep-linkable; the hub at `/analytics` is what the bottom bar and the
|
||||
// rail point at, and `alsoMatches` keeps it highlighted while you are inside any of them.
|
||||
NavDestination('/analytics', Icons.insights_outlined, Icons.insights, 'Аналитика', NavGroup.overview,
|
||||
alsoMatches: ['/income', '/rebalance', '/goals', '/tax'], primary: true),
|
||||
NavDestination('/events', Icons.event_note_outlined, Icons.event_note, 'События', NavGroup.ledger),
|
||||
NavDestination('/imports', Icons.upload_file_outlined, Icons.upload_file, 'Импорт', NavGroup.ledger,
|
||||
alsoMatches: ['/instruments/pending']),
|
||||
NavDestination('/cashflow', Icons.swap_horiz_outlined, Icons.swap_horiz, 'Потоки', NavGroup.ledger),
|
||||
NavDestination('/categories', Icons.category_outlined, Icons.category, 'Категории', NavGroup.ledger),
|
||||
NavDestination(
|
||||
'/transactions', Icons.receipt_long_outlined, Icons.receipt_long, 'Операции', NavGroup.ledger),
|
||||
NavDestination('/rules', Icons.rule_folder_outlined, Icons.rule_folder, 'Правила', NavGroup.ledger),
|
||||
NavDestination(
|
||||
'/health', Icons.monitor_heart_outlined, Icons.monitor_heart, 'Здоровье', NavGroup.system),
|
||||
NavDestination('/settings', Icons.settings_outlined, Icons.settings, 'Настройки', NavGroup.system),
|
||||
];
|
||||
Reference in New Issue
Block a user