feat: 补充我的页内容区块

This commit is contained in:
Zhou Weidong committed 2026-10-22 21:44:03 +08:00
1 parent 7971ac18fc
commit 9953c955e3
23 files changed
+800 -9

No files matched your search

@@ -0,0 +1,169 @@
// Author: fruit.com.cn
import 'package:flutter/material.dart';
import '../../../core/data/models/watch_progress.dart';
import '../../../core/theme/app_colors.dart';
import '../../../core/theme/app_dimens.dart';
import '../../../core/widgets/remote_image.dart';
import '../../../l10n/app_localizations.dart';
class MineContinueWatching extends StatelessWidget {
const MineContinueWatching({
super.key,
required this.records,
required this.onOpenAll,
});
final List<WatchProgress> records;
final VoidCallback onOpenAll;
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
final visible = records.take(10).toList(growable: false);
if (visible.isEmpty) return const SizedBox.shrink();
return Column(
key: const Key('mine-continue-watching'),
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.symmetric(
horizontal: AppDimens.mineGridHorizontal,
),
child: Row(
children: [
Expanded(
child: Text(
l10n.continueWatching,
style: const TextStyle(
color: AppColors.textPrimary,
fontSize: AppDimens.mineSectionTitleFont,
fontWeight: FontWeight.w600,
),
),
),
InkWell(
key: const Key('mine-continue-all'),
onTap: onOpenAll,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
l10n.all,
style: const TextStyle(
color: AppColors.textTertiary,
fontSize: AppDimens.mineSectionLinkFont,
),
),
const Icon(
Icons.chevron_right_rounded,
color: AppColors.textTertiary,
size: 16,
),
],
),
),
],
),
),
const SizedBox(height: AppDimens.spaceSm),
SizedBox(
height: AppDimens.mineContinueRowHeight,
child: ListView.separated(
key: const Key('mine-continue-list'),
scrollDirection: Axis.horizontal,
physics: const BouncingScrollPhysics(),
padding: const EdgeInsets.symmetric(
horizontal: AppDimens.mineGridHorizontal,
),
itemCount: visible.length,
separatorBuilder: (_, _) =>
const SizedBox(width: AppDimens.spaceMd),
itemBuilder: (_, index) {
final record = visible[index];
return _card(l10n, record);
},
),
),
],
);
}
Widget _card(AppLocalizations l10n, WatchProgress record) {
final title = record.dramaTitle?.trim() ?? '';
final episode = record.episodeNumber;
final hasProgress = record.duration > Duration.zero;
return SizedBox(
width: AppDimens.mineContinueCardWidth,
child: InkWell(
key: Key('mine-continue-${record.dramaId}'),
borderRadius: BorderRadius.circular(AppDimens.homeCardRadius),
onTap: onOpenAll,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AspectRatio(
aspectRatio: AppDimens.cardCoverAspectRatio,
child: Stack(
clipBehavior: Clip.hardEdge,
children: [
Positioned.fill(
child: ClipRRect(
borderRadius: BorderRadius.circular(
AppDimens.homeCardRadius,
),
child: RemoteImage(
record.dramaCover ?? '',
fit: BoxFit.cover,
showFallbackIcon: true,
fallbackColor: AppColors.contentSurface,
),
),
),
if (hasProgress)
Positioned(
left: 0,
right: 0,
bottom: 0,
child: LinearProgressIndicator(
value: record.fraction,
minHeight: AppDimens.followingProgressHeight,
backgroundColor: AppColors.inactiveTrack,
valueColor: const AlwaysStoppedAnimation<Color>(
AppColors.primary,
),
),
),
],
),
),
const SizedBox(height: AppDimens.cardCoverTitleGap),
Text(
title.isEmpty ? l10n.dataUnavailable : title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: AppColors.textPrimary,
fontSize: AppDimens.mineContinueTitleFont,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 2),
Text(
episode == null || episode <= 0
? l10n.dataUnavailable
: l10n.episodeLabel(episode),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: AppColors.textTertiary,
fontSize: AppDimens.mineContinueMetaFont,
),
),
],
),
),
);
}
}
@@ -0,0 +1,93 @@
// Author: fruit.com.cn
import 'package:flutter/material.dart';
import '../../../core/theme/app_colors.dart';
import '../../../core/theme/app_dimens.dart';
import '../../../l10n/app_localizations.dart';
class MineMoreServices extends StatelessWidget {
const MineMoreServices({
super.key,
required this.onCommonQuestions,
required this.onAbout,
});
final VoidCallback onCommonQuestions;
final VoidCallback onAbout;
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return Container(
key: const Key('mine-more-services'),
decoration: BoxDecoration(
color: AppColors.minePanelSurface,
borderRadius: BorderRadius.circular(AppDimens.mineGridRadius),
),
clipBehavior: Clip.antiAlias,
child: Column(
children: [
_row(
key: const Key('mine-common-questions'),
label: l10n.commonQuestions,
onTap: onCommonQuestions,
),
const Divider(
height: AppDimens.mineGridDividerWidth,
thickness: AppDimens.mineGridDividerWidth,
color: AppColors.mineMoreDivider,
),
_row(
key: const Key('mine-about'),
label: l10n.aboutUs,
onTap: onAbout,
),
],
),
);
}
Widget _row({
required Key key,
required String label,
required VoidCallback onTap,
}) {
return Material(
color: AppColors.transparent,
child: InkWell(
key: key,
onTap: onTap,
child: SizedBox(
height: AppDimens.mineMoreRowHeight,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: AppDimens.minePanelPadding,
),
child: Row(
children: [
Expanded(
child: Text(
label,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: AppColors.textPrimary,
fontSize: AppDimens.mineMoreFont,
fontWeight: FontWeight.w400,
),
),
),
const Icon(
Icons.chevron_right_rounded,
color: AppColors.textSecondary,
size: 18,
),
],
),
),
),
),
);
}
}
@@ -0,0 +1,91 @@
// Author: fruit.com.cn
import 'package:flutter/material.dart';
import '../../../core/theme/app_colors.dart';
import '../../../core/theme/app_dimens.dart';
import '../../../l10n/app_localizations.dart';
class MineStatsPanel extends StatelessWidget {
const MineStatsPanel({
super.key,
required this.followingCount,
required this.watchRecordCount,
required this.coinBalance,
});
final int? followingCount;
final int? watchRecordCount;
final int? coinBalance;
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return Container(
key: const Key('mine-stats'),
padding: const EdgeInsets.all(AppDimens.minePanelPadding),
decoration: BoxDecoration(
color: AppColors.minePanelSurface,
borderRadius: BorderRadius.circular(AppDimens.mineGridRadius),
),
child: Row(
children: [
_stat(
key: const Key('mine-stat-following'),
value: followingCount,
label: l10n.followingCountLabel,
l10n: l10n,
),
_stat(
key: const Key('mine-stat-watch-records'),
value: watchRecordCount,
label: l10n.watchRecordsCountLabel,
l10n: l10n,
),
_stat(
key: const Key('mine-stat-coins'),
value: coinBalance,
label: l10n.coinBalance,
l10n: l10n,
),
],
),
);
}
Widget _stat({
required Key key,
required int? value,
required String label,
required AppLocalizations l10n,
}) {
return Expanded(
child: Column(
key: key,
children: [
Text(
value?.toString() ?? l10n.dataUnavailable,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: AppColors.textPrimary,
fontSize: AppDimens.mineStatsValueFont,
fontWeight: FontWeight.w700,
),
),
const SizedBox(height: AppDimens.spaceXs),
Text(
label,
maxLines: 1,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.center,
style: const TextStyle(
color: AppColors.textTertiary,
fontSize: AppDimens.mineStatsLabelFont,
),
),
],
),
);
}
}