feat: 补充我的页内容区块
This commit is contained in:
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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user