feat(app): offline-кэш на остальных экранах — фаза 5
accounts, cashflow, categories, goals, income, portfolio (+instrument), rebalance, tax, rules переведены на Cached<T> по контракту docs/ai/offline-cache.md. Общие/второстепенные селекторы (scopesProvider, categoriesListProvider и т.п.) оставлены как есть — не основной контент экрана. sync_page.dart и settings_page.dart не тронуты (первый — заменён health-page отдельно, второй — чистые действия без списка для баннера).
This commit is contained in:
@@ -9,6 +9,7 @@ import '../../core/utils/ru_date.dart';
|
||||
import '../../core/widgets/async_value_view.dart';
|
||||
import '../../core/widgets/empty_state.dart';
|
||||
import '../../core/widgets/money_text.dart';
|
||||
import '../../core/widgets/stale_banner.dart';
|
||||
import 'labels.dart';
|
||||
import 'providers.dart';
|
||||
|
||||
@@ -28,43 +29,47 @@ class InstrumentPage extends ConsumerWidget {
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(detail.valueOrNull?.instrument.ticker ??
|
||||
detail.valueOrNull?.instrument.name ??
|
||||
title: Text(detail.valueOrNull?.data.instrument.ticker ??
|
||||
detail.valueOrNull?.data.instrument.name ??
|
||||
'Инструмент'),
|
||||
),
|
||||
body: AsyncValueView(
|
||||
value: detail,
|
||||
onRetry: () => ref.invalidate(instrumentProvider(instrumentId)),
|
||||
data: (d) => ListView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
children: [
|
||||
_Header(instrument: d.instrument, holding: d.holding),
|
||||
const SizedBox(height: 16),
|
||||
_Section(
|
||||
title: 'Цена',
|
||||
child: d.prices.isEmpty
|
||||
? const EmptyState(
|
||||
icon: Icons.show_chart,
|
||||
message: 'Цен нет — стоимость позиции неизвестна.',
|
||||
)
|
||||
: _PriceChart(prices: d.prices),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
_Section(
|
||||
title: 'Лоты',
|
||||
child: d.lots.isEmpty
|
||||
? const EmptyState(icon: Icons.layers_outlined, message: 'Лотов нет.')
|
||||
: _LotsTable(lots: d.lots),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
_Section(
|
||||
title: 'События',
|
||||
child: d.events.isEmpty
|
||||
? const EmptyState(icon: Icons.receipt_long, message: 'Событий нет.')
|
||||
: _EventsTable(events: d.events),
|
||||
),
|
||||
],
|
||||
),
|
||||
data: (cached) {
|
||||
final d = cached.data;
|
||||
return ListView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
children: [
|
||||
if (cached.fetchedAt != null) StaleBanner(fetchedAt: cached.fetchedAt!),
|
||||
_Header(instrument: d.instrument, holding: d.holding),
|
||||
const SizedBox(height: 16),
|
||||
_Section(
|
||||
title: 'Цена',
|
||||
child: d.prices.isEmpty
|
||||
? const EmptyState(
|
||||
icon: Icons.show_chart,
|
||||
message: 'Цен нет — стоимость позиции неизвестна.',
|
||||
)
|
||||
: _PriceChart(prices: d.prices),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
_Section(
|
||||
title: 'Лоты',
|
||||
child: d.lots.isEmpty
|
||||
? const EmptyState(icon: Icons.layers_outlined, message: 'Лотов нет.')
|
||||
: _LotsTable(lots: d.lots),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
_Section(
|
||||
title: 'События',
|
||||
child: d.events.isEmpty
|
||||
? const EmptyState(icon: Icons.receipt_long, message: 'Событий нет.')
|
||||
: _EventsTable(events: d.events),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user