Initial commit

This commit is contained in:
admin1
2026-07-14 11:11:36 +08:00
commit 656499cf94
604 changed files with 119518 additions and 0 deletions
@@ -0,0 +1,172 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../core/contracts/library.dart';
import '../../../providers/di_providers.dart';
import '../../../providers/session_provider.dart';
import '../../../shared/utils/cross_server_search_key.dart';
import 'media_info_section.dart';
import 'multi_source_section.dart';
class SourceSelectionSection extends ConsumerWidget {
final CrossServerSearchReq? req;
final List<EmbyRawMediaSource> localSources;
final int selectedSourceIdx;
final CrossServerSourceCard? selectedCrossServerCard;
final String? currentServerName;
final String? excludedServerIdOverride;
final EmbyRawItem? localMediaInfoItem;
final void Function(EmbyRawMediaSource src, int index) onLocalSourceTap;
final void Function(CrossServerSourceCard card) onCrossServerSourceTap;
final String title;
final bool showLoadingWhenEmpty;
final bool externalLoading;
final String loadingText;
final Widget? leading;
const SourceSelectionSection({
super.key,
required this.req,
required this.localSources,
required this.selectedSourceIdx,
required this.selectedCrossServerCard,
required this.onLocalSourceTap,
required this.onCrossServerSourceTap,
this.currentServerName,
this.excludedServerIdOverride,
this.localMediaInfoItem,
this.title = '多源聚合',
this.showLoadingWhenEmpty = false,
this.externalLoading = false,
this.loadingText = '正在查找可用版本…',
this.leading,
});
@override
Widget build(BuildContext context, WidgetRef ref) {
final searchReq = req;
final card = selectedCrossServerCard;
final currentLocalSourceId =
card == null && selectedSourceIdx < localSources.length
? localSources[selectedSourceIdx].Id
: null;
final selectedCrossServerKey = card != null
? '${card.serverId}|${card.mediaSourceId}'
: null;
final EmbyRawMediaSource? mediaSource =
card?.mediaSource ??
(selectedSourceIdx < localSources.length
? localSources[selectedSourceIdx]
: null);
final EmbyRawItem? mediaItem = card?.item ?? localMediaInfoItem;
final multiSourceVisibility = searchReq == null
? _MultiSourceVisibility.hidden
: _multiSourceVisibility(ref, searchReq);
final mediaInfo =
mediaSource != null &&
mediaItem != null &&
MediaInfoSection.hasVisibleContent(
item: mediaItem,
selectedSource: mediaSource,
)
? MediaInfoSection(
item: mediaItem,
selectedSource: mediaSource,
embedded: true,
)
: null;
final bool hasContent =
multiSourceVisibility != _MultiSourceVisibility.hidden ||
mediaInfo != null;
if (!hasContent) return const SizedBox.shrink();
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (leading != null) ...[leading!, const SizedBox(height: 16)],
if (searchReq != null)
MultiSourceSection(
req: searchReq,
embedded: true,
localSources: localSources,
currentLocalSourceId: currentLocalSourceId,
currentServerName: currentServerName,
excludedServerIdOverride: excludedServerIdOverride,
selectedCrossServerKey: selectedCrossServerKey,
title: title,
showLoadingWhenEmpty: showLoadingWhenEmpty,
externalLoading: externalLoading,
loadingText: loadingText,
onLocalSourceTap: onLocalSourceTap,
onSourceTap: (serverId, itemId, {mediaSourceId, card}) async {
if (card != null) onCrossServerSourceTap(card);
},
),
if (mediaInfo != null) mediaInfo,
],
);
}
_MultiSourceVisibility _multiSourceVisibility(
WidgetRef ref,
CrossServerSearchReq searchReq,
) {
final session = ref.watch(activeSessionProvider);
if (session == null) return _MultiSourceVisibility.hidden;
final key = (
activeServerId: session.serverId,
anchorType: searchReq.anchorType,
providerIdQuery: encodeProviderIdsForKey(searchReq.providerIds),
seriesProviderIdQuery: encodeProviderIdsForKey(
searchReq.seriesProviderIds ?? const {},
),
parentIndexNumber: searchReq.parentIndexNumber ?? -1,
indexNumber: searchReq.indexNumber ?? -1,
itemName: searchReq.itemName,
excludedServerId: excludedServerIdOverride ?? session.serverId,
);
final asyncResult = ref.watch(crossServerSearchDataProvider(key));
final remoteCards = asyncResult.maybeWhen(
data: (list) => list,
orElse: () => asyncResult.value ?? const <CrossServerSourceCard>[],
);
if (localSources.isNotEmpty || remoteCards.isNotEmpty) {
return _MultiSourceVisibility.content;
}
final loading = externalLoading || asyncResult.isLoading;
if (showLoadingWhenEmpty && loading) return _MultiSourceVisibility.loading;
return _MultiSourceVisibility.hidden;
}
}
enum _MultiSourceVisibility { hidden, loading, content }