Files
ReelShort/lib/pages/mine/widgets/mine_stats_panel.dart
T

231 lines
6.4 KiB
Dart

// Author: fruit.com.cn
// 展示可点击的金币、观看记录与收藏三列个人数据。
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:provider/provider.dart';
import '../../../core/data/services/auth_controller.dart';
import '../../../core/data/services/favorite_controller.dart';
import '../../../core/data/services/wallet_controller.dart';
import '../../../core/theme/app_colors.dart';
import '../../../core/theme/app_dimens.dart';
import '../../../l10n/app_localizations.dart';
import 'mine_stat_icon.dart';
const int _labelMaxLines = 2;
const double _dividerTop = 36;
/// 三项个人数据入口的现有页面回调。
class MineStatsActions {
/// 绑定钱包、观看记录与收藏页面。
const MineStatsActions({
required this.onWallet,
required this.onHistory,
required this.onFavorites,
});
final VoidCallback onWallet;
final VoidCallback onHistory;
final VoidCallback onFavorites;
}
/// 将共享的账号、钱包和收藏状态绑定到个人数据区。
class MineStatsSection extends StatelessWidget {
/// 观看记录由我的页逻辑提供,其余状态沿用全局 Provider。
const MineStatsSection({
super.key,
required this.watchRecordCount,
required this.actions,
});
final int? watchRecordCount;
final MineStatsActions actions;
@override
Widget build(BuildContext context) {
final signedIn = context.watch<AuthController>().isSignedIn;
final wallet = context.watch<WalletController>();
final favorites = context.watch<FavoriteController>();
return MineStatsPanel(
coinBalance: signedIn && wallet.error == null
? wallet.snapshot?.wallet.coinBalance
: null,
watchRecordCount: watchRecordCount,
favoriteCount: favorites.loaded && favorites.error == null
? favorites.total
: null,
actions: actions,
);
}
}
/// 头像与会员卡之间的个人数据,未知值与真实零值明确区分。
class MineStatsPanel extends StatelessWidget {
/// 使用真实服务端总数与共享钱包余额构建数据区。
const MineStatsPanel({
super.key,
required this.coinBalance,
required this.watchRecordCount,
required this.favoriteCount,
required this.actions,
});
final int? coinBalance;
final int? watchRecordCount;
final int? favoriteCount;
final MineStatsActions actions;
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return Row(
key: const Key('mine-stats'),
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_tile(
type: MineStatIconType.coins,
value: coinBalance,
label: l10n.coinBalance,
onTap: actions.onWallet,
),
_divider(),
_tile(
type: MineStatIconType.history,
value: watchRecordCount,
label: l10n.watchRecordsCountLabel,
onTap: actions.onHistory,
),
_divider(),
_tile(
type: MineStatIconType.favorites,
value: favoriteCount,
label: l10n.myFavorites,
onTap: actions.onFavorites,
),
],
);
}
Widget _tile({
required MineStatIconType type,
required int? value,
required String label,
required VoidCallback onTap,
}) {
return Expanded(
child: _MineStatTile(
type: type,
value: value,
label: label,
onTap: onTap,
),
);
}
Widget _divider() {
return Padding(
padding: const EdgeInsets.only(top: _dividerTop),
child: Container(
width: AppDimens.mineStatsDividerWidth,
height: AppDimens.mineStatsDividerHeight,
color: AppColors.contentBorder,
),
);
}
}
class _MineStatTile extends StatelessWidget {
const _MineStatTile({
required this.type,
required this.value,
required this.label,
required this.onTap,
});
final MineStatIconType type;
final int? value;
final String label;
final VoidCallback onTap;
String get _key => switch (type) {
MineStatIconType.coins => 'mine-stat-coins',
MineStatIconType.history => 'mine-stat-watch-records',
MineStatIconType.favorites => 'mine-stat-favorites',
};
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
final formatted = value == null
? l10n.dataUnavailable
: NumberFormat.decimalPattern(l10n.localeName).format(value);
return Semantics(
button: true,
label: label,
value: formatted,
onTap: onTap,
excludeSemantics: true,
child: Material(
color: AppColors.transparent,
child: InkWell(
key: ValueKey(_key),
onTap: onTap,
excludeFromSemantics: true,
borderRadius: BorderRadius.circular(AppDimens.innerRadius),
child: ConstrainedBox(
constraints: const BoxConstraints(
minHeight: AppDimens.mineStatsMinHeight,
),
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: AppDimens.spaceXs,
vertical: AppDimens.spaceSm,
),
child: _content(formatted),
),
),
),
),
);
}
Widget _content(String formatted) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
MineStatIcon(key: ValueKey('$_key-icon'), type: type),
const SizedBox(height: AppDimens.spaceXs),
FittedBox(
fit: BoxFit.scaleDown,
child: Text(
formatted,
key: ValueKey('$_key-value'),
maxLines: 1,
style: const TextStyle(
fontFamily: 'Outfit',
color: AppColors.textPrimary,
fontSize: AppDimens.mineStatsValueFont,
fontWeight: FontWeight.w600,
fontFeatures: [FontFeature.tabularFigures()],
height: AppDimens.mineStatsValueLineHeight,
),
),
),
const SizedBox(height: AppDimens.spaceXs),
Text(
label,
maxLines: _labelMaxLines,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.center,
style: const TextStyle(
color: AppColors.textTertiary,
fontSize: AppDimens.mineStatsLabelFont,
height: AppDimens.mineStatsLabelLineHeight,
),
),
],
);
}
}