Files
ReelShort/lib/pages/following/following_grid_card.dart
T

182 lines
5.5 KiB
Dart

// Author: fruit.com.cn
import 'package:flutter/material.dart';
import '../../core/formatting/count_format.dart';
import '../../core/theme/app_colors.dart';
import '../../core/theme/app_dimens.dart';
import '../../core/widgets/remote_image.dart';
class FollowingGridCard extends StatelessWidget {
const FollowingGridCard({
super.key,
required this.dramaId,
required this.title,
required this.posterUrl,
required this.viewCount,
required this.tags,
required this.subtitle,
required this.onTap,
this.progress,
this.editing = false,
this.onRemove,
});
final String dramaId;
final String title;
final String posterUrl;
final int viewCount;
final List<String> tags;
final String subtitle;
final VoidCallback onTap;
final double? progress;
final bool editing;
final VoidCallback? onRemove;
@override
Widget build(BuildContext context) {
return InkWell(
key: Key('following-card-$dramaId'),
borderRadius: BorderRadius.circular(AppDimens.cardCoverRadius),
onTap: onTap,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
AspectRatio(
key: Key('following-cover-$dramaId'),
aspectRatio: AppDimens.cardCoverAspectRatio,
child: Stack(
clipBehavior: Clip.none,
children: [
Positioned.fill(
child: ClipRRect(
borderRadius: BorderRadius.circular(
AppDimens.cardCoverRadius,
),
child: RemoteImage(
posterUrl,
fit: BoxFit.cover,
showFallbackIcon: true,
fallbackColor: AppColors.contentSurface,
),
),
),
if (editing)
Positioned(
top: -6,
right: -6,
child: GestureDetector(
key: Key('following-remove-$dramaId'),
onTap: onRemove,
child: const CircleAvatar(
radius: 13,
backgroundColor: AppColors.contentChip,
child: Icon(
Icons.remove_rounded,
color: AppColors.textPrimary,
size: 18,
),
),
),
),
],
),
),
const SizedBox(height: AppDimens.cardCoverTitleGap),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: Text(
title,
maxLines: 2,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.left,
style: const TextStyle(
color: AppColors.textPrimary,
fontSize: AppDimens.cardTitleFont,
fontWeight: FontWeight.w500,
height: AppDimens.cardTitleLineHeight,
),
),
),
const SizedBox(width: AppDimens.spaceXs),
_views(),
],
),
if (tags.isNotEmpty) ...[
const SizedBox(height: AppDimens.cardTitleTagGap),
_tags(),
],
const SizedBox(height: AppDimens.spaceXs),
Text(
subtitle,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: AppColors.textTertiary,
fontSize: AppDimens.cardViewsFont,
),
),
if (progress != null) ...[
const SizedBox(height: AppDimens.spaceXs),
ClipRRect(
borderRadius: BorderRadius.circular(AppDimens.progressRadius),
child: LinearProgressIndicator(
key: Key('following-progress-$dramaId'),
value: progress!.clamp(0.0, 1.0).toDouble(),
minHeight: 2,
backgroundColor: AppColors.inactiveTrack,
valueColor: const AlwaysStoppedAnimation<Color>(
AppColors.primary,
),
),
),
],
],
),
);
}
Widget _views() {
return Row(
key: Key('following-views-$dramaId'),
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Icon(
Icons.auto_awesome_rounded,
color: AppColors.textPrimary,
size: AppDimens.cardViewsIcon,
),
const SizedBox(width: AppDimens.cardPlayCountGap),
Text(
formatPlayCount(viewCount),
style: const TextStyle(
color: AppColors.textPrimary,
fontSize: AppDimens.cardPlayCountFont,
fontWeight: FontWeight.w400,
),
),
],
);
}
Widget _tags() {
final visible = tags.take(3).toList(growable: false);
return Text(
visible.join(' | '),
key: Key('following-tags-$dramaId'),
maxLines: 1,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.left,
style: const TextStyle(
color: AppColors.textTertiary,
fontSize: AppDimens.cardTagFont,
),
);
}
}