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,204 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../core/contracts/library.dart';
import '../../providers/di_providers.dart';
import 'android/android_media_info_section.dart';
import 'widgets/additional_parts_section.dart';
import 'widgets/emby_cast_section.dart';
import 'widgets/episode_list_section.dart';
import 'widgets/similar_section.dart';
import 'widgets/special_features_section.dart';
import 'emby_detail_helpers.dart';
import 'model/detail_section_model.dart';
import 'model/episode_auto_select_args.dart';
import 'widgets/source_selection_section.dart';
List<DetailSection> buildEmbyDetailSections({
required WidgetRef ref,
required EmbyRawItem baseItem,
required EmbyRawItem displayItem,
required String? seriesId,
required String similarItemId,
required TmdbMediaRef? tmdbMediaRef,
required String? detailServerId,
required String? initialSeasonId,
required EmbyRawItem? tmdbSeriesItem,
required List<EmbyRawMediaSource> sortedSources,
required int selectedSourceIdx,
required CrossServerSourceCard? selectedCrossServerCard,
required String? currentServerName,
required ValueChanged<EmbyRawItem> onEpisodeTap,
required ValueChanged<int> onSelectSourceIndex,
required ValueChanged<CrossServerSourceCard> onSelectCrossServerSource,
EmbyRawMediaSource? activeSource,
List<String> studioNames = const [],
List<String> directors = const [],
Widget? sectionLeading,
}) {
return <DetailSection>[
if (seriesId != null)
DetailSection(
id: 'emby_episodes',
child: _buildEpisodesSection(
ref: ref,
baseItem: baseItem,
displayItem: displayItem,
seriesId: seriesId,
tmdbMediaRef: tmdbMediaRef,
detailServerId: detailServerId,
initialSeasonId: initialSeasonId,
tmdbSeriesItem: tmdbSeriesItem,
onEpisodeTap: onEpisodeTap,
leading: sectionLeading,
),
),
if (displayItem.Type == 'Movie' || displayItem.Type == 'Episode')
DetailSection(
id: 'emby_source',
child: Padding(
padding: const EdgeInsets.only(top: 24),
child: _buildSourceSection(
baseItem: baseItem,
displayItem: displayItem,
tmdbSeriesItem: tmdbSeriesItem,
sortedSources: sortedSources,
selectedSourceIdx: selectedSourceIdx,
selectedCrossServerCard: selectedCrossServerCard,
currentServerName: currentServerName,
onSelectSourceIndex: onSelectSourceIndex,
onSelectCrossServerSource: onSelectCrossServerSource,
leading: sectionLeading,
),
),
),
DetailSection(
id: 'emby_cast',
child: EmbyCastSection(
item: baseItem,
embedded: true,
leading: sectionLeading,
),
),
DetailSection(
id: 'emby_media_detail',
child: AndroidMediaInfoSection(
source: activeSource,
studioNames: studioNames,
directors: directors,
leading: sectionLeading,
),
),
if (baseItem.Type != 'Series')
DetailSection(
id: 'emby_additional_parts',
child: AdditionalPartsSection(
itemId: displayItem.Id,
embedded: true,
leading: sectionLeading,
),
),
if (baseItem.Type != 'Series')
DetailSection(
id: 'emby_special_features',
child: SpecialFeaturesSection(
itemId: displayItem.Id,
embedded: true,
leading: sectionLeading,
),
),
DetailSection(
id: 'emby_similar',
child: SimilarSection(
itemId: similarItemId,
embedded: true,
leading: sectionLeading,
),
),
];
}
Widget _buildEpisodesSection({
required WidgetRef ref,
required EmbyRawItem baseItem,
required EmbyRawItem displayItem,
required String seriesId,
required TmdbMediaRef? tmdbMediaRef,
required String? detailServerId,
required String? initialSeasonId,
required EmbyRawItem? tmdbSeriesItem,
required ValueChanged<EmbyRawItem> onEpisodeTap,
Widget? leading,
}) {
final showItem = baseItem.Type == 'Series' ? baseItem : tmdbSeriesItem;
final resumeAsync = baseItem.Type == 'Series' && detailServerId != null
? ref.watch(
seriesResumeTargetProvider((
serverId: detailServerId,
seriesId: seriesId,
)),
)
: null;
final autoSelect = EpisodeAutoSelectArgs.fromResume(resumeAsync);
return EpisodeListSection(
seriesId: seriesId,
tmdbSeriesId: tmdbMediaRef != null && tmdbMediaRef.mediaType == 'tv'
? tmdbMediaRef.id
: null,
currentEpisodeId: displayItem.Type == 'Episode' ? displayItem.Id : null,
currentEpisodeSeasonId: displayItem.Type == 'Episode'
? displayItem.SeasonId
: null,
currentEpisodeSeasonNumber: displayItem.Type == 'Episode'
? (displayItem.extra['ParentIndexNumber'] as num?)?.toInt()
: null,
currentEpisodeIndexNumber: displayItem.Type == 'Episode'
? displayItem.IndexNumber
: null,
initialSeasonId: initialSeasonId,
autoSelectFirstEpisode: autoSelect.autoSelectFirstEpisode,
autoSelectEpisodeId: autoSelect.autoSelectEpisodeId,
autoSelectSeasonId: autoSelect.autoSelectSeasonId,
onEpisodeTap: onEpisodeTap,
embedded: true,
showProviderIds: showItem != null
? lowercaseProviderIds(providerIdsOfItem(showItem))
: null,
showTitle: showItem?.Name,
showYear: showItem?.ProductionYear,
leading: leading,
);
}
Widget _buildSourceSection({
required EmbyRawItem baseItem,
required EmbyRawItem displayItem,
required EmbyRawItem? tmdbSeriesItem,
required List<EmbyRawMediaSource> sortedSources,
required int selectedSourceIdx,
required CrossServerSourceCard? selectedCrossServerCard,
required String? currentServerName,
required ValueChanged<int> onSelectSourceIndex,
required ValueChanged<CrossServerSourceCard> onSelectCrossServerSource,
Widget? leading,
}) {
final seriesItemForReq = baseItem.Type == 'Series'
? baseItem
: tmdbSeriesItem;
final req = buildCrossServerReqForDetail(displayItem, seriesItemForReq);
return SourceSelectionSection(
req: req,
localSources: sortedSources,
selectedSourceIdx: selectedSourceIdx,
selectedCrossServerCard: selectedCrossServerCard,
currentServerName: currentServerName,
localMediaInfoItem: displayItem,
showLoadingWhenEmpty: true,
loadingText: '正在查找可用版本…',
onLocalSourceTap: (src, index) => onSelectSourceIndex(index),
onCrossServerSourceTap: onSelectCrossServerSource,
leading: leading,
);
}