Files
ReelShort/lib/pages/mine/widgets/mine_continue_watching.dart
T
Vicky a3f3481f4f feat: phase1 UI prep — 设备ID系统传递 + 国家本地化展示 + 继续观看直进播放页
设备标识: Android SSAID/ iOS IDFV 原生通道 → Dart DeviceIdProvider
国家解析: 注册/登录从后端 IP → 国家/语言/时区/币种 自动落库
账号资料: 全部本地化展示 (account_display.dart)
我的页: 继续观看卡片点击直接进播放页
2026-10-27 15:45:20 +08:00

182 lines
6.0 KiB
Dart

// 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.onOpenRecord,
required this.onOpenAll,
});
final List<WatchProgress> records;
/// 单张卡片:直接进入该剧的播放页,并定位到记录的集。
final ValueChanged<WatchProgress> onOpenRecord;
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,
Localizations.localeOf(context).languageCode,
);
},
),
),
],
);
}
Widget _card(
AppLocalizations l10n,
WatchProgress record,
String languageCode,
) {
final title = record.localizedTitle(languageCode).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: () => onOpenRecord(record),
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,
),
),
],
),
),
);
}
}