Files
eryao/apps/lib/features/settings/presentation/screens/coin_center_screen.dart
T
qzl ff40ff9dd8 feat: 新人初始礼包购买追踪功能
- 数据库:添加 has_purchased_starter_pack 字段到 register_bonus_claims
- 后端:创建静态配置管理套餐信息,支持按国家/地区区分
- 后端:新增 GET /api/v1/points/packages API 返回可用套餐
- 后端:创建 utils/paths.py 统一路径管理
- 前端:动态获取套餐信息,移除硬编码
- 前端:添加 ProductCode 枚举约束,前后端类型安全
- 配置:Profile 默认国家改为 US(ISO 3166-1 alpha-2)
- 文档:更新协议文档说明新 API 和字段
2026-04-16 16:11:09 +08:00

175 lines
5.4 KiB
Dart

import 'package:flutter/material.dart';
import '../../../../app/di/injection.dart';
import '../../../../core/auth/session_store.dart';
import '../../../../core/logging/logger.dart';
import '../../../../data/network/api_client.dart';
import '../../../../data/storage/local_kv_store.dart';
import '../../../../l10n/app_localizations.dart';
import '../../../../shared/theme/app_color_palette.dart';
import '../../../../shared/theme/design_tokens.dart';
import '../../../points/data/apis/points_api.dart';
import '../../../points/data/models/package_info.dart';
import '../widgets/settings_section_widgets.dart';
class CoinCenterScreen extends StatefulWidget {
const CoinCenterScreen({super.key, required this.balance});
final int balance;
@override
State<CoinCenterScreen> createState() => _CoinCenterScreenState();
}
class _CoinCenterScreenState extends State<CoinCenterScreen> {
final Logger _logger = getLogger('features.settings.coin_center_screen');
List<PackageInfo>? _packages;
bool _isLoading = true;
@override
void initState() {
super.initState();
_loadPackages();
}
Future<void> _loadPackages() async {
try {
final sessionStore = SessionStore(LocalKvStore());
final apiClient = ApiClient(
baseUrl: appDependencies.backendUrl,
tokenProvider: sessionStore.getToken,
);
final api = PointsApi(apiClient.rawDio);
final result = await api.getPackages();
if (mounted) {
setState(() {
_packages = result.packages;
_isLoading = false;
});
}
} catch (e, stackTrace) {
_logger.error(
message: 'Failed to load packages',
error: e,
stackTrace: stackTrace,
);
if (mounted) {
setState(() {
_isLoading = false;
});
}
}
}
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
final colors = Theme.of(context).colorScheme;
final palette = Theme.of(context).extension<AppColorPalette>()!;
return Scaffold(
backgroundColor: colors.surfaceContainerLow,
appBar: AppBar(
title: Text(l10n.settingsCoinCenterTitle),
centerTitle: true,
backgroundColor: colors.surfaceContainerLow,
surfaceTintColor: colors.surfaceContainerLow,
),
body: ListView(
padding: const EdgeInsets.all(AppSpacing.lg),
children: [
Container(
padding: const EdgeInsets.all(AppSpacing.xl),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(AppRadius.xl),
gradient: LinearGradient(
colors: [colors.primary, palette.accentPurple],
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(
Icons.monetization_on_rounded,
color: colors.onPrimary,
size: 34,
),
const SizedBox(height: AppSpacing.md),
Text(
l10n.settingsCoinBalanceLabel,
style: Theme.of(
context,
).textTheme.bodyMedium?.copyWith(color: colors.onPrimary),
),
const SizedBox(height: AppSpacing.xs),
Text(
l10n.settingsCoinBalanceValue(widget.balance),
style: Theme.of(
context,
).textTheme.headlineMedium?.copyWith(color: colors.onPrimary),
),
const SizedBox(height: AppSpacing.sm),
Text(
l10n.settingsCoinCenterDescription,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: colors.onPrimary.withValues(alpha: 0.88),
),
),
],
),
),
const SizedBox(height: AppSpacing.xl),
SectionLabel(text: l10n.settingsCoinRechargeSection),
..._buildPackageCards(l10n),
],
),
);
}
List<Widget> _buildPackageCards(AppLocalizations l10n) {
if (_isLoading) {
return [
const Padding(
padding: EdgeInsets.all(AppSpacing.xl),
child: Center(child: CircularProgressIndicator()),
),
];
}
if (_packages == null || _packages!.isEmpty) {
return [];
}
return List.generate(_packages!.length, (index) {
final pkg = _packages![index];
return Column(
children: [
if (index > 0) const SizedBox(height: AppSpacing.md),
CoinPackageCard(
title: _getPackageTitle(pkg, l10n),
price: pkg.priceDisplay,
amount: pkg.credits,
badge: _getPackageBadge(pkg, l10n),
),
],
);
});
}
String? _getPackageBadge(PackageInfo pkg, AppLocalizations l10n) {
if (pkg.productCode == ProductCode.popularPack) {
return l10n.settingsCoinPackPopularBadge;
}
return null;
}
String _getPackageTitle(PackageInfo pkg, AppLocalizations l10n) {
return switch (pkg.productCode) {
ProductCode.newUserPack => l10n.settingsCoinPackStarter,
ProductCode.basicPack => l10n.settingsCoinPackBasic,
ProductCode.popularPack => l10n.settingsCoinPackPopular,
ProductCode.premiumPack => l10n.settingsCoinPackPremium,
};
}
}