Initial commit
This commit is contained in:
@@ -0,0 +1,194 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../core/contracts/library.dart';
|
||||
import '../theme/app_theme.dart';
|
||||
import 'hover_scrollable_row.dart';
|
||||
import 'media_row_metrics.dart';
|
||||
import 'media_poster_card.dart';
|
||||
|
||||
class LatestMediaRow extends StatefulWidget {
|
||||
final String title;
|
||||
final String? subtitle;
|
||||
final List<EmbyRawItem> items;
|
||||
final String? Function(EmbyRawItem) imageUrlBuilder;
|
||||
final String? Function(EmbyRawItem)? preloadImageUrlBuilder;
|
||||
final void Function(EmbyRawItem) onItemTap;
|
||||
final VoidCallback? onSeeAll;
|
||||
final int? totalCount;
|
||||
final MediaCardVariant cardVariant;
|
||||
final bool enableContextMenu;
|
||||
final Map<String, bool>? favoriteMap;
|
||||
final Map<String, bool>? playedMap;
|
||||
final Map<String, bool>? favoriteLoadingMap;
|
||||
final Map<String, bool>? playedLoadingMap;
|
||||
final void Function(EmbyRawItem)? onAddFavorite;
|
||||
final void Function(EmbyRawItem)? onMarkPlayed;
|
||||
final void Function(EmbyRawItem)? onHideFromResume;
|
||||
final Map<String, bool>? hideFromResumeLoadingMap;
|
||||
final bool showUnplayedCountBadge;
|
||||
final bool showHoverPlayIcon;
|
||||
final String? activeItemId;
|
||||
final String? fallbackImageUrl;
|
||||
final Map<String, String>? imageHeaders;
|
||||
|
||||
|
||||
final Color? titleColor;
|
||||
|
||||
const LatestMediaRow({
|
||||
super.key,
|
||||
required this.title,
|
||||
this.subtitle,
|
||||
required this.items,
|
||||
required this.imageUrlBuilder,
|
||||
this.preloadImageUrlBuilder,
|
||||
required this.onItemTap,
|
||||
this.onSeeAll,
|
||||
this.totalCount,
|
||||
this.cardVariant = MediaCardVariant.poster,
|
||||
this.enableContextMenu = false,
|
||||
this.favoriteMap,
|
||||
this.playedMap,
|
||||
this.favoriteLoadingMap,
|
||||
this.playedLoadingMap,
|
||||
this.onAddFavorite,
|
||||
this.onMarkPlayed,
|
||||
this.onHideFromResume,
|
||||
this.hideFromResumeLoadingMap,
|
||||
this.showUnplayedCountBadge = true,
|
||||
this.showHoverPlayIcon = true,
|
||||
this.activeItemId,
|
||||
this.fallbackImageUrl,
|
||||
this.imageHeaders,
|
||||
this.titleColor,
|
||||
});
|
||||
|
||||
@override
|
||||
State<LatestMediaRow> createState() => _LatestMediaRowState();
|
||||
}
|
||||
|
||||
class _LatestMediaRowState extends State<LatestMediaRow> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final compact = MediaRowMetrics.isCompact(context);
|
||||
final rowHeight = MediaRowMetrics.rowHeight(
|
||||
widget.cardVariant,
|
||||
compact: compact,
|
||||
);
|
||||
final cardWidth = MediaRowMetrics.cardWidth(
|
||||
widget.cardVariant,
|
||||
compact: compact,
|
||||
);
|
||||
final theme = Theme.of(context);
|
||||
final tokens = context.appTokens;
|
||||
final titleColor = widget.titleColor;
|
||||
final mutedColor = titleColor != null
|
||||
? titleColor.withValues(alpha: 0.55)
|
||||
: tokens.mutedForeground;
|
||||
final seeAllColor = titleColor != null
|
||||
? titleColor.withValues(alpha: 0.78)
|
||||
: theme.colorScheme.onSurface.withValues(alpha: 0.82);
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(24, 12, 24, 10),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
widget.title,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: theme.textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
color: titleColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (widget.subtitle != null) ...[
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'· ${widget.subtitle}',
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: mutedColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
if (widget.onSeeAll != null)
|
||||
TextButton(
|
||||
onPressed: widget.onSeeAll,
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: seeAllColor,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
visualDensity: VisualDensity.compact,
|
||||
),
|
||||
child: Text(
|
||||
widget.totalCount != null && widget.totalCount! > 0
|
||||
? '查看全部 ${widget.totalCount}'
|
||||
: '查看全部',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: rowHeight,
|
||||
child: HoverScrollableRow(
|
||||
builder: (_, controller) => ListView.separated(
|
||||
controller: controller,
|
||||
scrollDirection: Axis.horizontal,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
itemCount: widget.items.length,
|
||||
separatorBuilder: (_, index) => const SizedBox(width: 14),
|
||||
itemBuilder: (_, index) {
|
||||
final item = widget.items[index];
|
||||
final isActive = widget.activeItemId == item.Id;
|
||||
return MediaPosterCard(
|
||||
item: item,
|
||||
imageUrl: widget.imageUrlBuilder(item),
|
||||
onTap: () => widget.onItemTap(item),
|
||||
width: cardWidth,
|
||||
variant: widget.cardVariant,
|
||||
showHoverPlayIcon: widget.showHoverPlayIcon,
|
||||
enableContextMenu: widget.enableContextMenu,
|
||||
isFavoriteOverride:
|
||||
widget.favoriteMap?[item.Id] ?? item.UserData?.IsFavorite,
|
||||
isPlayedOverride:
|
||||
widget.playedMap?[item.Id] ?? item.UserData?.Played,
|
||||
favoriteLoading: widget.favoriteLoadingMap?[item.Id] ?? false,
|
||||
playedLoading: widget.playedLoadingMap?[item.Id] ?? false,
|
||||
onAddFavorite: widget.onAddFavorite != null
|
||||
? () => widget.onAddFavorite!(item)
|
||||
: null,
|
||||
onMarkPlayed: widget.onMarkPlayed != null
|
||||
? () => widget.onMarkPlayed!(item)
|
||||
: null,
|
||||
onHideFromResume: widget.onHideFromResume != null
|
||||
? () => widget.onHideFromResume!(item)
|
||||
: null,
|
||||
hideFromResumeLoading:
|
||||
widget.hideFromResumeLoadingMap?[item.Id] ?? false,
|
||||
showUnplayedCountBadge: widget.showUnplayedCountBadge,
|
||||
isActive: isActive,
|
||||
fallbackImageUrl: widget.fallbackImageUrl,
|
||||
preloadImageUrl: widget.preloadImageUrlBuilder?.call(item),
|
||||
imageHeaders: widget.imageHeaders,
|
||||
textColor: widget.titleColor,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user