231 lines
6.4 KiB
Dart
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,
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|