refactor: share search field, auth dialog, and list-download plumbing
The search screens, the out-of-band auth dialogs, the live TV guide and the list-download paths each carried their own copy of the same shell. Extracts SearchInputField and PendingAuthDialog and routes the duplicated download and guide helpers through one implementation.
This commit is contained in:
@@ -148,14 +148,7 @@ Future<DownloadResult?> showDownloadOptionsAndQueue(
|
||||
}
|
||||
|
||||
if (filter == DownloadFilter.unwatched && kind == MediaKind.show && context.mounted) {
|
||||
final syncChoice = await showOptionPickerDialog<_SyncChoice>(
|
||||
context,
|
||||
title: t.downloads.downloadNow,
|
||||
options: [
|
||||
(icon: Symbols.download_rounded, label: t.downloads.downloadOnce, value: _SyncChoice.downloadOnce),
|
||||
(icon: Symbols.sync_rounded, label: t.downloads.keepSynced, value: _SyncChoice.keepSynced),
|
||||
],
|
||||
);
|
||||
final syncChoice = await _showSyncChoiceDialog(context);
|
||||
if (syncChoice == null || !context.mounted) return null;
|
||||
keepSynced = syncChoice == _SyncChoice.keepSynced;
|
||||
}
|
||||
@@ -226,22 +219,12 @@ Future<DownloadResult?> showListDownloadOptionsAndQueue(
|
||||
final selectedFilter = await showOptionPickerDialog<DownloadFilter>(
|
||||
context,
|
||||
title: t.downloads.downloadNow,
|
||||
options: [
|
||||
(icon: Symbols.download_rounded, label: t.downloads.allEpisodes, value: DownloadFilter.all),
|
||||
(icon: Symbols.visibility_off_rounded, label: t.downloads.unwatchedOnly, value: DownloadFilter.unwatched),
|
||||
],
|
||||
options: _filterOptions(DownloadFilter.all, DownloadFilter.unwatched),
|
||||
);
|
||||
|
||||
if (selectedFilter == null || !context.mounted) return null;
|
||||
|
||||
final syncChoice = await showOptionPickerDialog<_SyncChoice>(
|
||||
context,
|
||||
title: t.downloads.downloadNow,
|
||||
options: [
|
||||
(icon: Symbols.download_rounded, label: t.downloads.downloadOnce, value: _SyncChoice.downloadOnce),
|
||||
(icon: Symbols.sync_rounded, label: t.downloads.keepSynced, value: _SyncChoice.keepSynced),
|
||||
],
|
||||
);
|
||||
final syncChoice = await _showSyncChoiceDialog(context);
|
||||
if (syncChoice == null || !context.mounted) return null;
|
||||
|
||||
final serverId = rootMetadata.serverId ?? client.serverId;
|
||||
@@ -279,36 +262,21 @@ Future<DownloadResult?> showListDownloadOptionsAndQueue(
|
||||
);
|
||||
}
|
||||
|
||||
/// Shows the shared list-download dialog for a playlist.
|
||||
Future<DownloadResult?> showPlaylistDownloadOptionsAndQueue(
|
||||
BuildContext context, {
|
||||
required MediaItem playlistMetadata,
|
||||
required List<MediaItem> items,
|
||||
required MediaServerClient client,
|
||||
required DownloadProvider downloadProvider,
|
||||
}) => showListDownloadOptionsAndQueue(
|
||||
context,
|
||||
rootMetadata: playlistMetadata,
|
||||
targetType: ContentTypes.playlist,
|
||||
items: items,
|
||||
client: client,
|
||||
downloadProvider: downloadProvider,
|
||||
);
|
||||
/// The all/unwatched option rows, shared by the pickers that differ only in
|
||||
/// how they spell those two values.
|
||||
List<({IconData? icon, String label, T value})> _filterOptions<T>(T all, T unwatched) => [
|
||||
(icon: Symbols.download_rounded, label: t.downloads.allEpisodes, value: all),
|
||||
(icon: Symbols.visibility_off_rounded, label: t.downloads.unwatchedOnly, value: unwatched),
|
||||
];
|
||||
|
||||
/// Shows the shared list-download dialog for a collection.
|
||||
Future<DownloadResult?> showCollectionDownloadOptionsAndQueue(
|
||||
BuildContext context, {
|
||||
required MediaItem collectionMetadata,
|
||||
required List<MediaItem> items,
|
||||
required MediaServerClient client,
|
||||
required DownloadProvider downloadProvider,
|
||||
}) => showListDownloadOptionsAndQueue(
|
||||
/// Asks whether to download once or keep the target synced.
|
||||
Future<_SyncChoice?> _showSyncChoiceDialog(BuildContext context) => showOptionPickerDialog<_SyncChoice>(
|
||||
context,
|
||||
rootMetadata: collectionMetadata,
|
||||
targetType: ContentTypes.collection,
|
||||
items: items,
|
||||
client: client,
|
||||
downloadProvider: downloadProvider,
|
||||
title: t.downloads.downloadNow,
|
||||
options: [
|
||||
(icon: Symbols.download_rounded, label: t.downloads.downloadOnce, value: _SyncChoice.downloadOnce),
|
||||
(icon: Symbols.sync_rounded, label: t.downloads.keepSynced, value: _SyncChoice.keepSynced),
|
||||
],
|
||||
);
|
||||
|
||||
Future<int?> _showEpisodeCountDialog(
|
||||
@@ -375,10 +343,7 @@ Future<bool> editSyncRuleFilter(
|
||||
final selected = await showOptionPickerDialog<String>(
|
||||
context,
|
||||
title: t.downloads.editSyncFilter,
|
||||
options: [
|
||||
(icon: Symbols.download_rounded, label: t.downloads.allEpisodes, value: SyncRuleFilter.all),
|
||||
(icon: Symbols.visibility_off_rounded, label: t.downloads.unwatchedOnly, value: SyncRuleFilter.unwatched),
|
||||
],
|
||||
options: _filterOptions(SyncRuleFilter.all, SyncRuleFilter.unwatched),
|
||||
);
|
||||
if (selected == null || selected == currentFilter || !context.mounted) return false;
|
||||
|
||||
|
||||
@@ -45,15 +45,15 @@ List<LiveTvChannelGroup> groupLiveTvChannelsBySource(List<LiveTvChannel> channel
|
||||
}
|
||||
|
||||
String liveTvChannelSourceKey(LiveTvChannel channel) {
|
||||
final serverId = _nonEmpty(channel.serverId) ?? '';
|
||||
final providerSource = _nonEmpty(channel.favoriteSource) ?? _nonEmpty(channel.lineup) ?? '';
|
||||
final dvrSource = _nonEmpty(channel.liveDvrKey) ?? '';
|
||||
final serverId = liveTvNonEmpty(channel.serverId) ?? '';
|
||||
final providerSource = liveTvNonEmpty(channel.favoriteSource) ?? liveTvNonEmpty(channel.lineup) ?? '';
|
||||
final dvrSource = liveTvNonEmpty(channel.liveDvrKey) ?? '';
|
||||
return '$serverId\u0000$providerSource\u0000$dvrSource';
|
||||
}
|
||||
|
||||
String liveTvChannelSourceLabel(LiveTvChannel channel) {
|
||||
final serverLabel = _nonEmpty(channel.serverName) ?? _nonEmpty(channel.serverId) ?? 'Live TV';
|
||||
final sourceTitle = _nonEmpty(channel.liveTvSourceTitle);
|
||||
final serverLabel = liveTvNonEmpty(channel.serverName) ?? liveTvNonEmpty(channel.serverId) ?? 'Live TV';
|
||||
final sourceTitle = liveTvNonEmpty(channel.liveTvSourceTitle);
|
||||
if (sourceTitle == null || sourceTitle == serverLabel) return serverLabel;
|
||||
return '$serverLabel - $sourceTitle';
|
||||
}
|
||||
@@ -62,8 +62,8 @@ String _deduplicatedLabel(LiveTvChannelGroup group) {
|
||||
if (group.channels.isEmpty) return group.label;
|
||||
final first = group.channels.first;
|
||||
final suffixes = [
|
||||
_nonEmpty(first.liveTvSourceTitle),
|
||||
_nonEmpty(first.liveDvrKey),
|
||||
liveTvNonEmpty(first.liveTvSourceTitle),
|
||||
liveTvNonEmpty(first.liveDvrKey),
|
||||
liveTvProviderIdentifierForChannel(first),
|
||||
];
|
||||
String? suffix;
|
||||
@@ -76,8 +76,3 @@ String _deduplicatedLabel(LiveTvChannelGroup group) {
|
||||
if (suffix == null || group.label.contains(suffix)) return group.label;
|
||||
return '${group.label} - $suffix';
|
||||
}
|
||||
|
||||
String? _nonEmpty(String? value) {
|
||||
final trimmed = value?.trim();
|
||||
return trimmed == null || trimmed.isEmpty ? null : trimmed;
|
||||
}
|
||||
|
||||
@@ -2,14 +2,14 @@ import '../models/livetv_channel.dart';
|
||||
import '../models/livetv_program.dart';
|
||||
|
||||
bool liveTvProgramMatchesChannel(LiveTvProgram program, LiveTvChannel channel) {
|
||||
final programChannel = _nonEmpty(program.channelIdentifier);
|
||||
final programChannel = liveTvNonEmpty(program.channelIdentifier);
|
||||
if (programChannel == null) return false;
|
||||
if (programChannel != channel.key && programChannel != channel.identifier) return false;
|
||||
|
||||
if (!_nullableIdsMatch(program.serverId, channel.serverId)) return false;
|
||||
if (!_nullableIdsMatch(program.liveDvrKey, channel.liveDvrKey)) return false;
|
||||
|
||||
final programProvider = _nonEmpty(program.providerIdentifier);
|
||||
final programProvider = liveTvNonEmpty(program.providerIdentifier);
|
||||
final channelProvider = liveTvProviderIdentifierForChannel(channel);
|
||||
if (programProvider != null && channelProvider != null && programProvider != channelProvider) return false;
|
||||
|
||||
@@ -17,26 +17,27 @@ bool liveTvProgramMatchesChannel(LiveTvProgram program, LiveTvChannel channel) {
|
||||
}
|
||||
|
||||
String? liveTvProviderIdentifierForChannel(LiveTvChannel channel) {
|
||||
final source = _nonEmpty(channel.favoriteSource);
|
||||
final source = liveTvNonEmpty(channel.favoriteSource);
|
||||
if (source != null) {
|
||||
final uri = Uri.tryParse(source);
|
||||
if (uri != null && uri.pathSegments.isNotEmpty) return _nonEmpty(uri.pathSegments.last);
|
||||
if (uri != null && uri.pathSegments.isNotEmpty) return liveTvNonEmpty(uri.pathSegments.last);
|
||||
|
||||
final slashIndex = source.lastIndexOf('/');
|
||||
if (slashIndex >= 0 && slashIndex < source.length - 1) {
|
||||
return _nonEmpty(source.substring(slashIndex + 1));
|
||||
return liveTvNonEmpty(source.substring(slashIndex + 1));
|
||||
}
|
||||
}
|
||||
return _nonEmpty(channel.lineup);
|
||||
return liveTvNonEmpty(channel.lineup);
|
||||
}
|
||||
|
||||
bool _nullableIdsMatch(String? a, String? b) {
|
||||
final left = _nonEmpty(a);
|
||||
final right = _nonEmpty(b);
|
||||
final left = liveTvNonEmpty(a);
|
||||
final right = liveTvNonEmpty(b);
|
||||
return left == null || right == null || left == right;
|
||||
}
|
||||
|
||||
String? _nonEmpty(String? value) {
|
||||
/// Trimmed [value], or null when it is null, empty, or whitespace-only.
|
||||
String? liveTvNonEmpty(String? value) {
|
||||
final trimmed = value?.trim();
|
||||
return trimmed == null || trimmed.isEmpty ? null : trimmed;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user