import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_ui/shadcn_ui.dart'; import '../../../common/domain/money.dart'; import '../../../common/push/notification_permission.dart'; import '../../../common/push/umeng_push_service.dart'; import '../../../common/widgets/adaptive.dart'; import '../../ads/application/startup_ad_service.dart'; import '../../ads/presentation/home_ad_banner.dart'; import '../../settings/application/update_controller.dart'; import '../../settings/application/update_service.dart'; import '../application/asset_portfolio_controller.dart'; class AssetsPage extends ConsumerStatefulWidget { const AssetsPage({super.key}); @override ConsumerState createState() => _AssetsPageState(); } class _AssetsPageState extends ConsumerState { @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { unawaited(_completeColdLaunch()); }); } Future _completeColdLaunch() async { if (!mounted) return; await StartupAdService.showIfAvailable(context, ref); if (!mounted) return; await _checkColdLaunchUpgrade(); if (mounted) await _requestNotificationPermission(); } Future _requestNotificationPermission() async { if (!mounted) return; if (defaultTargetPlatform == TargetPlatform.iOS) { // 研听的 U-Push register 会请求 APNs;这里再以系统 API 兜底,避免 // 旧安装已跳过欢迎页时永远不触发首次通知授权。iOS 不展示顶部说明。 await NotificationPermission.requestFromSettings(); } else { await NotificationPermission.requestIfNeeded(context); } await UmengPushService.refreshDeviceToken(); } Future _checkColdLaunchUpgrade() async { if (!mounted || !UpdateService.isSupported || !ref.read(updateSessionGateProvider).takeColdLaunchSlot()) { return; } try { final upgrade = await ref .read(updateControllerProvider.notifier) .checkUpgrade(); if (!mounted) return; await UpdateService.showIfNeeded( context, ref, upgrade, source: UpgradeCheckSource.automatic, ); } catch (_) { // Automatic update checks must never block the assets home page. } } @override Widget build(BuildContext context) { final summary = ref.watch(assetPortfolioControllerProvider); final cs = ShadTheme.of(context).colorScheme; return SafeArea( top: false, child: ListView( padding: const EdgeInsets.fromLTRB(18, 18, 18, 24), children: [ Center( child: ConstrainedBox( constraints: const BoxConstraints( maxWidth: Adaptive.contentMaxWidth, ), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ _Card( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('我的贵金属总估值', style: _mutedStyle(cs)), const SizedBox(height: 10), Text( formatCny(summary.totalValue), style: TextStyle( fontSize: 34, fontWeight: FontWeight.w800, color: cs.foreground, letterSpacing: 0, ), ), const SizedBox(height: 8), Text( '今日 ${formatCny(summary.todayChangeAmount)} · ${summary.todayChangePercent.toStringAsFixed(2)}%', style: TextStyle( fontSize: 13, color: summary.todayChangeAmount >= 0 ? const Color(0xFFC0392B) : const Color(0xFF2E8B6F), ), ), ], ), ), const SizedBox(height: 12), Wrap( spacing: 8, runSpacing: 8, children: [ for (final item in summary.breakdowns) Chip( label: Text( '${item.metal.shortLabel} ${formatCny(item.value)}', ), avatar: CircleAvatar( backgroundColor: item.metal.color, ), ), ], ), const SizedBox(height: 12), const HomeAdBanner(), const SizedBox(height: 12), _Card( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '我的持仓 · ${summary.holdings.length} 件 · ${formatGram(summary.totalWeightGram)}', style: TextStyle( fontSize: 15, fontWeight: FontWeight.w700, color: cs.foreground, ), ), const SizedBox(height: 12), for (final item in summary.holdings) Padding( padding: const EdgeInsets.symmetric(vertical: 8), child: Row( children: [ Expanded( child: Text( '${item.holding.name} · ${item.holding.purityLabel} · ${formatGram(item.holding.weightGram)}', style: TextStyle(color: cs.foreground), ), ), Text(formatCny(item.materialValue)), ], ), ), ], ), ), ], ), ), ), ], ), ); } } class _Card extends StatelessWidget { const _Card({required this.child}); final Widget child; @override Widget build(BuildContext context) { final cs = ShadTheme.of(context).colorScheme; return DecoratedBox( decoration: BoxDecoration( color: cs.card, border: Border.all(color: cs.border), borderRadius: BorderRadius.circular(12), ), child: Padding(padding: const EdgeInsets.all(16), child: child), ); } } TextStyle _mutedStyle(ShadColorScheme cs) { return TextStyle(fontSize: 13, color: cs.mutedForeground, letterSpacing: 0); }