fix(ui): 打磨推荐追剧我的设置页间距与图标
This commit is contained in:
1 parent
de7da64886
commit
5346be40a9
33 files changed
+1408
-811
No files matched your search
@@ -0,0 +1,207 @@
|
||||
// Author: fruit.com.cn
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../core/data/models/watch_record.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 ContinueWatchingBanner extends StatelessWidget {
|
||||
const ContinueWatchingBanner({
|
||||
super.key,
|
||||
required this.record,
|
||||
required this.languageCode,
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
final WatchRecord record;
|
||||
final String languageCode;
|
||||
final VoidCallback onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final episode = record.episodeNumber;
|
||||
final total =
|
||||
record.progress.totalEpisodes ?? record.drama?.episodeCount ?? 0;
|
||||
final title = record.localizedDisplayTitle(languageCode);
|
||||
final showMeta = episode > 0 && total > 0;
|
||||
|
||||
return SizedBox(
|
||||
height: AppDimens.followingBannerHeight,
|
||||
child: ClipRRect(
|
||||
borderRadius:
|
||||
BorderRadius.circular(AppDimens.followingBannerRadius),
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
RemoteImage(
|
||||
record.displayCover,
|
||||
fit: BoxFit.cover,
|
||||
showFallbackIcon: true,
|
||||
fallbackColor: AppColors.contentSurface,
|
||||
),
|
||||
const DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.centerLeft,
|
||||
end: Alignment.centerRight,
|
||||
colors: [
|
||||
Color(0xCC000000),
|
||||
Color(0x99000000),
|
||||
Color(0x00000000),
|
||||
],
|
||||
stops: [0, 0.45, 1],
|
||||
),
|
||||
),
|
||||
),
|
||||
Material(
|
||||
type: MaterialType.transparency,
|
||||
child: InkWell(
|
||||
key: const Key('following-continue-banner'),
|
||||
onTap: onTap,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(
|
||||
AppDimens.followingBannerInnerGap,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (title.isNotEmpty)
|
||||
Text(
|
||||
title,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
color: AppColors.bannerTitleWarm,
|
||||
fontSize:
|
||||
AppDimens.followingBannerTitleFont,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
if (showMeta) ...[
|
||||
const SizedBox(
|
||||
height: AppDimens.followingBannerMetaGap,
|
||||
),
|
||||
Text(
|
||||
l10n.continueWatchingSubtitle(episode, total),
|
||||
maxLines: 1,
|
||||
style: const TextStyle(
|
||||
color: Color(0xB3F5EFE4),
|
||||
fontSize:
|
||||
AppDimens.followingBannerSubtitleFont,
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(
|
||||
height: AppDimens.followingBannerButtonTopGap,
|
||||
),
|
||||
_ContinueButton(label: l10n.continueWatching),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: _ProgressBar(fraction: record.progress.fraction),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ContinueButton extends StatelessWidget {
|
||||
const _ContinueButton({required this.label});
|
||||
|
||||
final String label;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.brandRed,
|
||||
borderRadius: BorderRadius.circular(
|
||||
AppDimens.followingBannerButtonRadius,
|
||||
),
|
||||
),
|
||||
child: SizedBox(
|
||||
height: AppDimens.followingBannerButtonHeight,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal:
|
||||
AppDimens.followingBannerButtonHorizontalPadding,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.play_arrow_rounded,
|
||||
size: AppDimens.followingBannerButtonIcon,
|
||||
color: AppColors.onPrimary,
|
||||
),
|
||||
const SizedBox(width: AppDimens.followingBannerButtonGap),
|
||||
Text(
|
||||
label,
|
||||
style: const TextStyle(
|
||||
color: AppColors.onPrimary,
|
||||
fontSize: AppDimens.followingBannerButtonFont,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ProgressBar extends StatelessWidget {
|
||||
const _ProgressBar({required this.fraction});
|
||||
|
||||
final double fraction;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// SizedBox 给紧约束让 DecoratedBox 轨道撑满整宽;
|
||||
// 不能用无子 ColoredBox 直接放 Stack(会缩成 0×0 隐形)。
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.circular(
|
||||
AppDimens.followingBannerProgressHeight,
|
||||
),
|
||||
child: SizedBox(
|
||||
height: AppDimens.followingBannerProgressHeight,
|
||||
width: double.infinity,
|
||||
child: DecoratedBox(
|
||||
decoration: const BoxDecoration(
|
||||
color: Color(0x66FFFFFF),
|
||||
),
|
||||
child: Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: FractionallySizedBox(
|
||||
widthFactor: fraction.clamp(0.0, 1.0).toDouble(),
|
||||
// 无 child 的 ColoredBox 在松约束下会缩成 0×0,高度必须显式占满。
|
||||
heightFactor: 1.0,
|
||||
child: const ColoredBox(color: AppColors.brandRed),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user