refactor: clean up download code duplication
This commit is contained in:
@@ -214,7 +214,6 @@ class DownloadProvider extends ChangeNotifier {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@override
|
||||
@override
|
||||
void dispose() {
|
||||
_progressSubscription?.cancel();
|
||||
@@ -222,6 +221,10 @@ class DownloadProvider extends ChangeNotifier {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
/// Ensure metadata has a serverId, falling back to a parent's serverId.
|
||||
PlexMetadata _ensureServerId(PlexMetadata metadata, String? fallbackServerId) =>
|
||||
metadata.serverId != null ? metadata : metadata.copyWith(serverId: fallbackServerId);
|
||||
|
||||
/// All current download progress entries
|
||||
Map<String, DownloadProgress> get downloads => Map.unmodifiable(_downloads);
|
||||
|
||||
@@ -333,23 +336,18 @@ class DownloadProvider extends ChangeNotifier {
|
||||
.toList();
|
||||
}
|
||||
|
||||
/// Get all episode downloads (any status) for a specific show
|
||||
List<DownloadProgress> _getEpisodeDownloadsForShow(String showRatingKey) {
|
||||
/// Get episode downloads filtered by show and/or season ratingKey.
|
||||
List<DownloadProgress> _getEpisodeDownloads({
|
||||
String? showRatingKey,
|
||||
String? seasonRatingKey,
|
||||
}) {
|
||||
return _downloads.entries
|
||||
.where((entry) {
|
||||
final meta = _metadata[entry.key];
|
||||
return meta?.type == 'episode' && meta?.grandparentRatingKey == showRatingKey;
|
||||
})
|
||||
.map((entry) => entry.value)
|
||||
.toList();
|
||||
}
|
||||
|
||||
/// Get all episode downloads (any status) for a specific season
|
||||
List<DownloadProgress> _getEpisodeDownloadsForSeason(String seasonRatingKey) {
|
||||
return _downloads.entries
|
||||
.where((entry) {
|
||||
final meta = _metadata[entry.key];
|
||||
return meta?.type == 'episode' && meta?.parentRatingKey == seasonRatingKey;
|
||||
if (meta?.type != 'episode') return false;
|
||||
if (showRatingKey != null && meta?.grandparentRatingKey != showRatingKey) return false;
|
||||
if (seasonRatingKey != null && meta?.parentRatingKey != seasonRatingKey) return false;
|
||||
return true;
|
||||
})
|
||||
.map((entry) => entry.value)
|
||||
.toList();
|
||||
@@ -361,7 +359,7 @@ class DownloadProvider extends ChangeNotifier {
|
||||
return _calculateAggregateProgress(
|
||||
serverId: serverId,
|
||||
ratingKey: showRatingKey,
|
||||
episodes: _getEpisodeDownloadsForShow(showRatingKey),
|
||||
episodes: _getEpisodeDownloads(showRatingKey: showRatingKey),
|
||||
entityType: 'show',
|
||||
);
|
||||
}
|
||||
@@ -372,7 +370,7 @@ class DownloadProvider extends ChangeNotifier {
|
||||
return _calculateAggregateProgress(
|
||||
serverId: serverId,
|
||||
ratingKey: seasonRatingKey,
|
||||
episodes: _getEpisodeDownloadsForSeason(seasonRatingKey),
|
||||
episodes: _getEpisodeDownloads(seasonRatingKey: seasonRatingKey),
|
||||
entityType: 'season',
|
||||
);
|
||||
}
|
||||
@@ -511,12 +509,12 @@ class DownloadProvider extends ChangeNotifier {
|
||||
if (meta == null) {
|
||||
// No metadata stored yet, might be a show/season being queued
|
||||
// Check if any episodes exist for this as a parent
|
||||
final episodesAsShow = _getEpisodeDownloadsForShow(ratingKey);
|
||||
final episodesAsShow = _getEpisodeDownloads(showRatingKey: ratingKey);
|
||||
if (episodesAsShow.isNotEmpty) {
|
||||
return getAggregateProgressForShow(serverId, ratingKey);
|
||||
}
|
||||
|
||||
final episodesAsSeason = _getEpisodeDownloadsForSeason(ratingKey);
|
||||
final episodesAsSeason = _getEpisodeDownloads(seasonRatingKey: ratingKey);
|
||||
if (episodesAsSeason.isNotEmpty) {
|
||||
return getAggregateProgressForSeason(serverId, ratingKey);
|
||||
}
|
||||
@@ -691,109 +689,61 @@ class DownloadProvider extends ChangeNotifier {
|
||||
Future<void> _fetchAndStoreParentMetadata(PlexMetadata episode, PlexClient client) async {
|
||||
final serverId = episode.serverId;
|
||||
if (serverId == null) return;
|
||||
|
||||
await _fetchAndStoreRelatedMetadata(serverId: serverId, ratingKey: episode.grandparentRatingKey, client: client);
|
||||
await _fetchAndStoreRelatedMetadata(serverId: serverId, ratingKey: episode.parentRatingKey, client: client);
|
||||
}
|
||||
|
||||
/// Fetch, persist, and download artwork for a related metadata item (show or season).
|
||||
Future<void> _fetchAndStoreRelatedMetadata({
|
||||
required String serverId,
|
||||
required String? ratingKey,
|
||||
required PlexClient client,
|
||||
}) async {
|
||||
if (ratingKey == null) return;
|
||||
final globalKey = buildGlobalKey(serverId, ratingKey);
|
||||
final storageService = DownloadStorageService.instance;
|
||||
|
||||
// Fetch and store show metadata if not already stored
|
||||
final showRatingKey = episode.grandparentRatingKey;
|
||||
if (showRatingKey != null) {
|
||||
final showGlobalKey = buildGlobalKey(serverId, showRatingKey);
|
||||
|
||||
// Try to use existing metadata (set when queueing an entire show)
|
||||
PlexMetadata? showMetadata = _metadata[showGlobalKey];
|
||||
|
||||
// If not already cached, fetch full metadata with images
|
||||
if (showMetadata == null) {
|
||||
try {
|
||||
showMetadata = await client.getMetadataWithImages(showRatingKey);
|
||||
} catch (e) {
|
||||
appLogger.w('Failed to fetch show metadata for $showRatingKey', error: e);
|
||||
}
|
||||
}
|
||||
|
||||
if (showMetadata != null) {
|
||||
final showWithServer = showMetadata.copyWith(serverId: serverId);
|
||||
_metadata[showGlobalKey] = showWithServer;
|
||||
|
||||
// Persist to database/API cache for offline usage
|
||||
await _downloadManager.saveMetadata(showWithServer);
|
||||
|
||||
// Ensure show artwork is downloaded even if metadata already existed
|
||||
final thumbPath = showWithServer.thumb;
|
||||
final hasPoster = thumbPath != null && await storageService.artworkExists(serverId, thumbPath);
|
||||
if (!hasPoster) {
|
||||
await _downloadManager.downloadArtworkForMetadata(showWithServer, client);
|
||||
appLogger.d('Downloaded show artwork for $showGlobalKey');
|
||||
}
|
||||
|
||||
// Store artwork reference in provider's map for offline display
|
||||
_artworkPaths[showGlobalKey] = DownloadedArtwork(thumbPath: thumbPath);
|
||||
PlexMetadata? metadata = _metadata[globalKey];
|
||||
if (metadata == null) {
|
||||
try {
|
||||
metadata = await client.getMetadataWithImages(ratingKey);
|
||||
} catch (e) {
|
||||
appLogger.w('Failed to fetch metadata for $ratingKey', error: e);
|
||||
}
|
||||
}
|
||||
if (metadata == null) return;
|
||||
|
||||
// Fetch and store season metadata if not already stored
|
||||
final seasonRatingKey = episode.parentRatingKey;
|
||||
if (seasonRatingKey != null) {
|
||||
final seasonGlobalKey = buildGlobalKey(serverId, seasonRatingKey);
|
||||
PlexMetadata? seasonMetadata = _metadata[seasonGlobalKey];
|
||||
final withServer = metadata.copyWith(serverId: serverId);
|
||||
_metadata[globalKey] = withServer;
|
||||
await _downloadManager.saveMetadata(withServer);
|
||||
|
||||
if (seasonMetadata == null) {
|
||||
try {
|
||||
seasonMetadata = await client.getMetadataWithImages(seasonRatingKey);
|
||||
} catch (e) {
|
||||
appLogger.w('Failed to fetch season metadata for $seasonRatingKey', error: e);
|
||||
}
|
||||
}
|
||||
final thumbPath = withServer.thumb;
|
||||
final hasPoster = thumbPath != null && await storageService.artworkExists(serverId, thumbPath);
|
||||
if (!hasPoster) {
|
||||
await _downloadManager.downloadArtworkForMetadata(withServer, client);
|
||||
}
|
||||
_artworkPaths[globalKey] = DownloadedArtwork(thumbPath: thumbPath);
|
||||
}
|
||||
|
||||
if (seasonMetadata != null) {
|
||||
final seasonWithServer = seasonMetadata.copyWith(serverId: serverId);
|
||||
_metadata[seasonGlobalKey] = seasonWithServer;
|
||||
|
||||
// Persist to database/API cache for offline usage
|
||||
await _downloadManager.saveMetadata(seasonWithServer);
|
||||
|
||||
// Ensure season artwork is downloaded even if metadata already existed
|
||||
final thumbPath = seasonWithServer.thumb;
|
||||
final hasPoster = thumbPath != null && await storageService.artworkExists(serverId, thumbPath);
|
||||
if (!hasPoster) {
|
||||
await _downloadManager.downloadArtworkForMetadata(seasonWithServer, client);
|
||||
appLogger.d('Downloaded season artwork for $seasonGlobalKey');
|
||||
}
|
||||
|
||||
// Store artwork reference in provider's map for offline display
|
||||
_artworkPaths[seasonGlobalKey] = DownloadedArtwork(thumbPath: thumbPath);
|
||||
}
|
||||
/// Store leafCount for a show or season so aggregate progress works.
|
||||
Future<void> _storeLeafCount(String globalKey, PlexMetadata metadata) async {
|
||||
if (metadata.leafCount != null && metadata.leafCount! > 0) {
|
||||
_totalEpisodeCounts[globalKey] = metadata.leafCount!;
|
||||
await _persistTotalEpisodeCount(globalKey, metadata.leafCount!);
|
||||
}
|
||||
}
|
||||
|
||||
/// Queue all episodes from a TV show for download
|
||||
Future<int> _queueShowDownload(PlexMetadata show, PlexClient client) async {
|
||||
final globalKey = show.globalKey;
|
||||
int count = 0;
|
||||
final seasons = await client.getChildren(show.ratingKey);
|
||||
|
||||
// Store total episode count from show metadata (leafCount)
|
||||
if (show.leafCount != null && show.leafCount! > 0) {
|
||||
_totalEpisodeCounts[globalKey] = show.leafCount!;
|
||||
await _persistTotalEpisodeCount(globalKey, show.leafCount!);
|
||||
appLogger.i(
|
||||
'💾 Stored episode count for show $globalKey: ${show.leafCount}\n'
|
||||
' - Show title: ${show.title}\n'
|
||||
' - Show type: ${show.type}\n'
|
||||
' - Total stored counts: ${_totalEpisodeCounts.length}',
|
||||
);
|
||||
} else {
|
||||
appLogger.w(
|
||||
'⚠️ Show $globalKey has no leafCount! Cannot store episode count.\n'
|
||||
' - Show title: ${show.title}\n'
|
||||
' - Show type: ${show.type}\n'
|
||||
' - leafCount value: ${show.leafCount}',
|
||||
);
|
||||
}
|
||||
await _storeLeafCount(show.globalKey, show);
|
||||
|
||||
for (final season in seasons) {
|
||||
if (season.type == 'season') {
|
||||
// Ensure season has serverId from parent show
|
||||
final seasonWithServer = season.serverId != null ? season : season.copyWith(serverId: show.serverId);
|
||||
final seasonWithServer = _ensureServerId(season, show.serverId);
|
||||
count += await _queueSeasonDownload(seasonWithServer, client);
|
||||
}
|
||||
}
|
||||
@@ -803,33 +753,14 @@ class DownloadProvider extends ChangeNotifier {
|
||||
|
||||
/// Queue all episodes from a season for download
|
||||
Future<int> _queueSeasonDownload(PlexMetadata season, PlexClient client) async {
|
||||
final globalKey = season.globalKey;
|
||||
int count = 0;
|
||||
final episodes = await client.getChildren(season.ratingKey);
|
||||
|
||||
// Store total episode count from season metadata (leafCount)
|
||||
if (season.leafCount != null && season.leafCount! > 0) {
|
||||
_totalEpisodeCounts[globalKey] = season.leafCount!;
|
||||
await _persistTotalEpisodeCount(globalKey, season.leafCount!);
|
||||
appLogger.i(
|
||||
'💾 Stored episode count for season $globalKey: ${season.leafCount}\n'
|
||||
' - Season title: ${season.title}\n'
|
||||
' - Season type: ${season.type}\n'
|
||||
' - Total stored counts: ${_totalEpisodeCounts.length}',
|
||||
);
|
||||
} else {
|
||||
appLogger.w(
|
||||
'⚠️ Season $globalKey has no leafCount! Cannot store episode count.\n'
|
||||
' - Season title: ${season.title}\n'
|
||||
' - Season type: ${season.type}\n'
|
||||
' - leafCount value: ${season.leafCount}',
|
||||
);
|
||||
}
|
||||
await _storeLeafCount(season.globalKey, season);
|
||||
|
||||
for (final episode in episodes) {
|
||||
if (episode.type == 'episode') {
|
||||
// Ensure episode has serverId from parent season
|
||||
final episodeWithServer = episode.serverId != null ? episode : episode.copyWith(serverId: season.serverId);
|
||||
final episodeWithServer = _ensureServerId(episode, season.serverId);
|
||||
await _queueSingleDownload(episodeWithServer, client);
|
||||
count++;
|
||||
}
|
||||
@@ -862,7 +793,7 @@ class DownloadProvider extends ChangeNotifier {
|
||||
|
||||
for (final season in seasons) {
|
||||
if (season.type == 'season') {
|
||||
final seasonWithServer = season.serverId != null ? season : season.copyWith(serverId: show.serverId);
|
||||
final seasonWithServer = _ensureServerId(season, show.serverId);
|
||||
queuedCount += await _queueMissingSeasonEpisodes(seasonWithServer, client);
|
||||
}
|
||||
}
|
||||
@@ -880,7 +811,7 @@ class DownloadProvider extends ChangeNotifier {
|
||||
|
||||
for (final episode in episodes) {
|
||||
if (episode.type == 'episode') {
|
||||
final episodeWithServer = episode.serverId != null ? episode : episode.copyWith(serverId: season.serverId);
|
||||
final episodeWithServer = _ensureServerId(episode, season.serverId);
|
||||
|
||||
final episodeGlobalKey = episodeWithServer.globalKey;
|
||||
|
||||
|
||||
@@ -646,10 +646,17 @@ class _MediaDetailScreenState extends State<MediaDetailScreen> with WatchStateAw
|
||||
onPressed: () async {
|
||||
final client = _getClientForMetadata(context);
|
||||
if (client == null) return;
|
||||
final count = await downloadProvider.queueDownload(metadata, client);
|
||||
if (context.mounted) {
|
||||
final message = count > 1 ? t.downloads.episodesQueued(count: count) : t.downloads.downloadQueued;
|
||||
showSuccessSnackBar(context, message);
|
||||
try {
|
||||
final count = await downloadProvider.queueDownload(metadata, client);
|
||||
if (context.mounted) {
|
||||
final message =
|
||||
count > 1 ? t.downloads.episodesQueued(count: count) : t.downloads.downloadQueued;
|
||||
showSuccessSnackBar(context, message);
|
||||
}
|
||||
} on CellularDownloadBlockedException {
|
||||
if (context.mounted) {
|
||||
showErrorSnackBar(context, t.settings.cellularDownloadBlocked);
|
||||
}
|
||||
}
|
||||
},
|
||||
icon: const AppIcon(Symbols.download_rounded, fill: 1),
|
||||
|
||||
@@ -234,6 +234,11 @@ class DownloadManagerService {
|
||||
// background_downloader state
|
||||
bool _fileDownloaderInitialized = false;
|
||||
static const _downloadGroup = 'video_downloads';
|
||||
static const _maxAppRetries = 3;
|
||||
static const _nativeRetries = 5;
|
||||
static const _autoRetryDelay = Duration(seconds: 30);
|
||||
static const _progressDebounceDelay = Duration(seconds: 2);
|
||||
static const _videoExtensions = {'.mp4', '.ogv', '.mkv', '.m4v', '.avi'};
|
||||
|
||||
// Keys currently being paused — prevents holding queue from promoting them
|
||||
final Set<String> _pausingKeys = {};
|
||||
@@ -435,12 +440,7 @@ class DownloadManagerService {
|
||||
if (metadata.type == 'episode' && metadata.grandparentRatingKey != null) {
|
||||
final parsed = parseGlobalKey(globalKey);
|
||||
if (parsed != null) {
|
||||
final showCached = await _apiCache.get(
|
||||
parsed.serverId,
|
||||
'/library/metadata/${metadata.grandparentRatingKey}',
|
||||
);
|
||||
final showJson = PlexCacheParser.extractFirstMetadata(showCached);
|
||||
if (showJson != null) showYear = PlexMetadata.fromJson(showJson).year;
|
||||
showYear = await _fetchShowYear(parsed.serverId, metadata.grandparentRatingKey);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -597,12 +597,9 @@ class DownloadManagerService {
|
||||
final ext = _getExtensionFromUrl(playbackData.videoUrl!) ?? 'mp4';
|
||||
|
||||
// Look up show year for episodes
|
||||
int? showYear;
|
||||
if (metadata.type == 'episode' && metadata.grandparentRatingKey != null) {
|
||||
final showCached = await _apiCache.get(serverId, '/library/metadata/${metadata.grandparentRatingKey}');
|
||||
final showJson = PlexCacheParser.extractFirstMetadata(showCached);
|
||||
if (showJson != null) showYear = PlexMetadata.fromJson(showJson).year;
|
||||
}
|
||||
final showYear = metadata.type == 'episode'
|
||||
? await _fetchShowYear(serverId, metadata.grandparentRatingKey)
|
||||
: null;
|
||||
|
||||
// Build display name for notifications
|
||||
final displayName = metadata.type == 'episode'
|
||||
@@ -641,7 +638,7 @@ class DownloadManagerService {
|
||||
group: _downloadGroup,
|
||||
updates: Updates.statusAndProgress,
|
||||
requiresWiFi: requiresWiFi,
|
||||
retries: 5,
|
||||
retries: _nativeRetries,
|
||||
metaData: globalKey,
|
||||
displayName: displayName,
|
||||
);
|
||||
@@ -682,7 +679,7 @@ class DownloadManagerService {
|
||||
group: _downloadGroup,
|
||||
updates: Updates.statusAndProgress,
|
||||
requiresWiFi: requiresWiFi,
|
||||
retries: 5,
|
||||
retries: _nativeRetries,
|
||||
allowPause: true,
|
||||
metaData: globalKey,
|
||||
displayName: displayName,
|
||||
@@ -706,7 +703,6 @@ class DownloadManagerService {
|
||||
} catch (e) {
|
||||
appLogger.e('Failed to prepare download for $globalKey', error: e);
|
||||
await _transitionStatus(globalKey, DownloadStatus.failed, errorMessage: e.toString());
|
||||
await _database.updateDownloadError(globalKey, e.toString());
|
||||
await _database.removeFromQueue(globalKey);
|
||||
_pendingDownloadContext.remove(globalKey);
|
||||
}
|
||||
@@ -745,7 +741,7 @@ class DownloadManagerService {
|
||||
// a 2-second settle period. The stream above provides real-time UI updates;
|
||||
// the DB write is only for crash-recovery state.
|
||||
_progressDebounceTimers[globalKey]?.cancel();
|
||||
_progressDebounceTimers[globalKey] = Timer(const Duration(seconds: 2), () {
|
||||
_progressDebounceTimers[globalKey] = Timer(_progressDebounceDelay, () {
|
||||
_progressDebounceTimers.remove(globalKey);
|
||||
_database.updateDownloadProgress(globalKey, progress, downloadedBytes, totalBytes).catchError((e) {
|
||||
appLogger.w('Failed to update download progress in DB', error: e);
|
||||
@@ -821,18 +817,17 @@ class DownloadManagerService {
|
||||
}
|
||||
final retryCount = existing?.retryCount ?? 0;
|
||||
|
||||
if (retryCount < 3 && _lastClient != null) {
|
||||
if (retryCount < _maxAppRetries && _lastClient != null) {
|
||||
// App-level auto-retry: schedule a fresh download after a delay.
|
||||
// Each new task gets 5 native retries with Range-based resume.
|
||||
appLogger.w(
|
||||
'Download failed for $globalKey (attempt ${retryCount + 1}/3), '
|
||||
'scheduling auto-retry in 30s: $errorMessage',
|
||||
'Download failed for $globalKey (attempt ${retryCount + 1}/$_maxAppRetries), '
|
||||
'scheduling auto-retry in ${_autoRetryDelay.inSeconds}s: $errorMessage',
|
||||
);
|
||||
await _transitionStatus(globalKey, DownloadStatus.failed, errorMessage: errorMessage);
|
||||
await _database.updateDownloadError(globalKey, errorMessage);
|
||||
await _database.removeFromQueue(globalKey);
|
||||
_autoRetryTimers[globalKey]?.cancel();
|
||||
_autoRetryTimers[globalKey] = Timer(const Duration(seconds: 30), () {
|
||||
_autoRetryTimers[globalKey] = Timer(_autoRetryDelay, () {
|
||||
_autoRetryTimers.remove(globalKey);
|
||||
_performAutoRetry(globalKey);
|
||||
});
|
||||
@@ -860,7 +855,6 @@ class DownloadManagerService {
|
||||
|
||||
appLogger.e('Download permanently failed for $globalKey: $errorMessage');
|
||||
await _transitionStatus(globalKey, DownloadStatus.failed, errorMessage: errorMessage);
|
||||
await _database.updateDownloadError(globalKey, errorMessage);
|
||||
await _database.removeFromQueue(globalKey);
|
||||
|
||||
// Try to enqueue more items from the queue
|
||||
@@ -1001,7 +995,6 @@ class DownloadManagerService {
|
||||
} catch (e) {
|
||||
appLogger.e('Post-download processing failed for $globalKey', error: e);
|
||||
await _transitionStatus(globalKey, DownloadStatus.failed, errorMessage: 'Post-processing failed: $e');
|
||||
await _database.updateDownloadError(globalKey, 'Post-processing failed: $e');
|
||||
await _database.removeFromQueue(globalKey);
|
||||
} finally {
|
||||
_completingKeys.remove(globalKey);
|
||||
@@ -1017,6 +1010,15 @@ class DownloadManagerService {
|
||||
return _apiCache.getMetadata(parsed.serverId, parsed.ratingKey);
|
||||
}
|
||||
|
||||
/// Look up the year of the parent show for an episode (used for folder naming).
|
||||
Future<int?> _fetchShowYear(String serverId, String? grandparentRatingKey) async {
|
||||
if (grandparentRatingKey == null) return null;
|
||||
final showCached = await _apiCache.get(serverId, '/library/metadata/$grandparentRatingKey');
|
||||
final showJson = PlexCacheParser.extractFirstMetadata(showCached);
|
||||
if (showJson != null) return PlexMetadata.fromJson(showJson).year;
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Re-derive the SAF file URI from metadata (for recovery when context is lost)
|
||||
Future<String?> _resolveSafStoredPath(PlexMetadata metadata, String ext, int? showYear) async {
|
||||
final safBaseUri = _storageService.safBaseUri;
|
||||
@@ -1256,6 +1258,9 @@ class DownloadManagerService {
|
||||
/// Default progress is 0 for most statuses, 100 for completed.
|
||||
Future<void> _transitionStatus(String globalKey, DownloadStatus status, {int? progress, String? errorMessage}) async {
|
||||
await _database.updateDownloadStatus(globalKey, status.index);
|
||||
if (status == DownloadStatus.failed && errorMessage != null) {
|
||||
await _database.updateDownloadError(globalKey, errorMessage);
|
||||
}
|
||||
_emitProgress(
|
||||
globalKey,
|
||||
status,
|
||||
@@ -1699,11 +1704,7 @@ class DownloadManagerService {
|
||||
final contents = await seasonDir.list().toList();
|
||||
final hasVideos = contents.any(
|
||||
(e) =>
|
||||
e.path.endsWith('.mp4') ||
|
||||
e.path.endsWith('.ogv') ||
|
||||
e.path.endsWith('.mkv') ||
|
||||
e.path.endsWith('.m4v') ||
|
||||
e.path.endsWith('.avi') ||
|
||||
_videoExtensions.any((ext) => e.path.endsWith(ext)) ||
|
||||
e.path.contains('_subs'),
|
||||
);
|
||||
|
||||
|
||||
@@ -33,15 +33,6 @@ class DeletionProgressDialog extends StatelessWidget {
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Progress text
|
||||
Text(
|
||||
'Deleting ${progress.itemTitle}... (${progress.currentItem} of ${progress.totalItems})',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Progress bar
|
||||
|
||||
Reference in New Issue
Block a user