Skip to content

Commit

Permalink
Use offline metadata on playlist add/remove menu. Minor fixes.
Browse files Browse the repository at this point in the history
  • Loading branch information
Komodo5197 committed May 19, 2024
1 parent b7c1169 commit 13cc3f6
Show file tree
Hide file tree
Showing 8 changed files with 89 additions and 48 deletions.
50 changes: 39 additions & 11 deletions lib/components/AddToPlaylistScreen/add_to_playlist_list.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'package:collection/collection.dart';
import 'package:finamp/components/Buttons/cta_medium.dart';
import 'package:finamp/components/PlayerScreen/queue_source_helper.dart';
import 'package:finamp/components/album_image.dart';
import 'package:finamp/services/downloads_service.dart';
import 'package:finamp/services/finamp_settings_helper.dart';
import 'package:finamp/services/jellyfin_api_helper.dart';
import 'package:flutter/material.dart';
Expand Down Expand Up @@ -163,23 +164,31 @@ class AddToPlaylistTile extends StatefulWidget {
class _AddToPlaylistTileState extends State<AddToPlaylistTile> {
String? playlistItemId;
int? childCount;
bool knownMissing = false;
bool? itemIsIncluded;

@override
void initState() {
super.initState();
if (!widget.isLoading) {
playlistItemId = widget.playlistItemId;
childCount = widget.playlist.childCount;
}
_updateState();
}

@override
void didUpdateWidget(AddToPlaylistTile oldWidget) {
super.didUpdateWidget(oldWidget);
_updateState();
}

void _updateState() {
if (!widget.isLoading) {
playlistItemId = widget.playlistItemId;
childCount = widget.playlist.childCount;
if (widget.playlistItemId != null) {
itemIsIncluded = true;
} else {
final downloadsService = GetIt.instance<DownloadsService>();
itemIsIncluded =
downloadsService.checkIfInCollection(widget.playlist, widget.song);
}
}
}

Expand All @@ -192,15 +201,33 @@ class _AddToPlaylistTileState extends State<AddToPlaylistTile> {
subtitle: AppLocalizations.of(context)!.songCount(childCount ?? 0),
leading: AlbumImage(item: widget.playlist),
positiveIcon: TablerIcons.circle_check_filled,
negativeIcon: knownMissing
? TablerIcons.circle_plus
negativeIcon: itemIsIncluded == null
// we don't actually know if the track is part of the playlist
: TablerIcons.circle_dashed_plus,
initialState: playlistItemId != null,
? TablerIcons.circle_dashed_plus
: TablerIcons.circle_plus,
initialState: itemIsIncluded ?? false,
onToggle: (bool currentState) async {
if (currentState) {
// If playlistItemId is null, we need to fetch from the server before we can remove
if (playlistItemId == null) {
throw "Cannot remove item from playlist, missing playlistItemId";
final jellyfinApiHelper = GetIt.instance<JellyfinApiHelper>();
var newItems = await jellyfinApiHelper.getItems(
parentItem: widget.playlist, fields: "");

playlistItemId = newItems
?.firstWhereOrNull((x) => x.id == widget.song.id)
?.playlistItemId;
if (playlistItemId == null) {
// We were already not part of the playlist,. so removal is complete
setState(() {
childCount = newItems?.length ?? 0;
itemIsIncluded = false;
});
return false;
}
if (!context.mounted) {
return true;
}
}
// part of playlist, remove
bool removed = await removeFromPlaylist(
Expand All @@ -209,7 +236,7 @@ class _AddToPlaylistTileState extends State<AddToPlaylistTile> {
if (removed) {
setState(() {
childCount = childCount == null ? null : childCount! - 1;
knownMissing = true;
itemIsIncluded = false;
});
}
return !removed;
Expand All @@ -226,6 +253,7 @@ class _AddToPlaylistTileState extends State<AddToPlaylistTile> {
playlistItemId = newItems
?.firstWhereOrNull((x) => x.id == widget.song.id)
?.playlistItemId;
itemIsIncluded = true;
});
return playlistItemId != null;
}
Expand Down
11 changes: 7 additions & 4 deletions lib/components/AlbumScreen/download_dialog.dart
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,14 @@ class DownloadDialog extends StatefulWidget {
final finampUserHelper = GetIt.instance<FinampUserHelper>();
viewId = finampUserHelper.currentUser!.currentViewId;
}
bool needTranscode =
FinampSettingsHelper.finampSettings.shouldTranscodeDownloads ==
bool needTranscode = FinampSettingsHelper
.finampSettings.shouldTranscodeDownloads ==
TranscodeDownloadsSetting.ask &&
// Skipp asking for transcode for image only collection
item.finampCollection?.type != FinampCollectionType.libraryImages;
// Skip asking for transcode for image only collection
item.finampCollection?.type != FinampCollectionType.libraryImages ||
// Skip asking for transcode for metadata +image collection
(item.finampCollection?.type != FinampCollectionType.allPlaylists &&
infoOnly);
String? downloadLocation =
FinampSettingsHelper.finampSettings.defaultDownloadLocation;
if (!FinampSettingsHelper.finampSettings.downloadLocationsMap
Expand Down
10 changes: 9 additions & 1 deletion lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -1504,5 +1504,13 @@
"description": "Subheader for adding to a playlist in the add to/remove from playlist popup menu"
},
"trackOfflineFavorites": "Sync all favorite statuses",
"trackOfflineFavoritesSubtitle": "This allows showing more up-to-date favorite statuses while offline. Does not download any additional files."
"trackOfflineFavoritesSubtitle": "This allows showing more up-to-date favorite statuses while offline. Does not download any additional files.",
"allPlaylistsInfoSetting": "Show all playlists offline",
"allPlaylistsInfoSettingSubtitle": "Sync metadata for all playlists to show partially downloaded playlists offline",
"downloadFavoritesSetting": "Download all favorites",
"downloadAllPlaylistsSetting": "Download all playlists",
"fiveLatestAlbumsSetting": "Download 5 latest albums",
"fiveLatestAlbumsSettingSubtitle": "Downloads will be removed as they age out. Lock the download to prevent an album from being removed.",
"cacheLibraryImagesSettings": "Cache current library images",
"cacheLibraryImagesSettingsSubtitle": "All album, artist, genre, and playlist covers in the currently active library will be downloaded."
}
5 changes: 4 additions & 1 deletion lib/models/jellyfin_models.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
///
/// These classes should be correct with Jellyfin 10.7.5
import 'package:finamp/models/finamp_models.dart';
import 'package:collection/collection.dart';
import 'package:finamp/models/finamp_models.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:hive/hive.dart';
Expand Down Expand Up @@ -2294,6 +2294,9 @@ class BaseItemDto with RunTimeTickDuration {
other.normalizationGain == normalizationGain &&
other.playlistItemId == playlistItemId;
}

DownloadItemType get downloadType =>
type! == "Audio" ? DownloadItemType.song : DownloadItemType.collection;
}

@JsonSerializable(
Expand Down
24 changes: 13 additions & 11 deletions lib/screens/downloads_settings_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ class DownloadsSettingsScreen extends StatelessWidget {
const SyncFavoritesSwitch(),
ListTile(
// TODO real UI for this
title: const Text("Show all playlists offline"),
subtitle: const Text(
"Sync metadata for all playlists to show partially downloaded playlists offline"),
title: Text(AppLocalizations.of(context)!.allPlaylistsInfoSetting),
subtitle: Text(
AppLocalizations.of(context)!.allPlaylistsInfoSettingSubtitle),
trailing: DownloadButton(
infoOnly: true,
item: DownloadStub.fromFinampCollection(
Expand All @@ -52,32 +52,34 @@ class DownloadsSettingsScreen extends StatelessWidget {
if (!Platform.isIOS) const ConcurentDownloadsSelector(),
ListTile(
// TODO real UI for this
title: const Text("Download all favorites"),
title: Text(AppLocalizations.of(context)!.downloadFavoritesSetting),
trailing: DownloadButton(
item: DownloadStub.fromFinampCollection(
FinampCollection(type: FinampCollectionType.favorites))),
),
ListTile(
// TODO real UI for this
title: const Text("Download all playlists"),
title:
Text(AppLocalizations.of(context)!.downloadAllPlaylistsSetting),
trailing: DownloadButton(
item: DownloadStub.fromFinampCollection(
FinampCollection(type: FinampCollectionType.allPlaylists))),
),
ListTile(
// TODO real UI for this
title: const Text("Download 5 latest albums"),
subtitle: const Text(
"Downloads will be removed as they age out. Lock the download to prevent an album from being removed."),
title: Text(AppLocalizations.of(context)!.fiveLatestAlbumsSetting),
subtitle: Text(
AppLocalizations.of(context)!.fiveLatestAlbumsSettingSubtitle),
trailing: DownloadButton(
item: DownloadStub.fromFinampCollection(FinampCollection(
type: FinampCollectionType.latest5Albums))),
),
ListTile(
// TODO real UI for this
title: const Text("Cache current library images"),
subtitle: const Text(
"All album, artist, genre, and playlist covers in the currently active library will be downloaded."),
title:
Text(AppLocalizations.of(context)!.cacheLibraryImagesSettings),
subtitle: Text(AppLocalizations.of(context)!
.cacheLibraryImagesSettingsSubtitle),
trailing: DownloadButton(
item: DownloadStub.fromFinampCollection(FinampCollection(
type: FinampCollectionType.libraryImages,
Expand Down
23 changes: 13 additions & 10 deletions lib/services/downloads_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import 'dart:async';
import 'dart:collection';
import 'dart:io';

import 'package:finamp/components/global_snackbar.dart';
import 'package:finamp/services/jellyfin_api_helper.dart';
import 'package:background_downloader/background_downloader.dart';
import 'package:collection/collection.dart';
import 'package:finamp/components/global_snackbar.dart';
import 'package:finamp/services/jellyfin_api_helper.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
Expand Down Expand Up @@ -289,7 +289,6 @@ class DownloadsService {
// If app is in background for more than 5 hours, treat it like a restart
// and potentially resync all items
if (FinampSettingsHelper.finampSettings.resyncOnStartup &&
!FinampSettingsHelper.finampSettings.isOffline &&
_appPauseTime != null &&
DateTime.now().difference(_appPauseTime!).inHours > 5) {
_isar.writeTxnSync(() {
Expand Down Expand Up @@ -365,8 +364,7 @@ class DownloadsService {
/// Begin processing stored downloads/deletes. This should only be called
/// after background_downloader is fully set up.
Future<void> startQueues() async {
if (FinampSettingsHelper.finampSettings.resyncOnStartup &&
!FinampSettingsHelper.finampSettings.isOffline) {
if (FinampSettingsHelper.finampSettings.resyncOnStartup) {
_isar.writeTxnSync(() {
syncBuffer.addAll([_anchor.isarId], [], null);
});
Expand Down Expand Up @@ -1253,6 +1251,15 @@ class DownloadsService {
.findFirstSync();
}

/// Check whether the given item is part of the given collection. Returns null
/// if there is no metadata for the collection.
bool? checkIfInCollection(BaseItemDto collection, BaseItemDto item) {
var parent = _isar.downloadItems.getSync(
DownloadStub.getHash(collection.id, DownloadItemType.collection));
var childId = DownloadStub.getHash(item.id, item.downloadType);
return parent?.orderedChildren?.contains(childId);
}

// This is for album/playlist screen
/// Get all songs in a collection, ordered correctly. Used to show songs on
/// album/playlist screen. Can return all songs in the album/playlist or
Expand Down Expand Up @@ -1438,11 +1445,7 @@ class DownloadsService {
}

bool? isFavorite(BaseItemDto item) {
var stubId = DownloadStub.getHash(
item.id,
item.type == "Audio"
? DownloadItemType.song
: DownloadItemType.collection);
var stubId = DownloadStub.getHash(item.id, item.downloadType);
return _getFavoriteIds()?.contains(stubId);
}

Expand Down
10 changes: 3 additions & 7 deletions lib/services/downloads_service_backend.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import 'dart:convert';
import 'dart:core';
import 'dart:io';

import 'package:finamp/components/global_snackbar.dart';
import 'package:finamp/services/downloads_service.dart';
import 'package:background_downloader/background_downloader.dart';
import 'package:collection/collection.dart';
import 'package:finamp/components/global_snackbar.dart';
import 'package:finamp/services/downloads_service.dart';
import 'package:flutter/scheduler.dart';
import 'package:get_it/get_it.dart';
import 'package:isar/isar.dart';
Expand Down Expand Up @@ -1410,11 +1410,7 @@ class DownloadsSyncService {
_downloadsService.resetConnectionErrors();
var stubList = outputItems
.map((e) => DownloadStub.fromItem(
item: e,
type: typeOverride ??
(e.type == "Audio"
? DownloadItemType.song
: DownloadItemType.collection)))
item: e, type: typeOverride ?? e.downloadType))
.toList();
for (var element in stubList) {
_metadataCache[element.id] = Future.value(element);
Expand Down
4 changes: 1 addition & 3 deletions lib/services/favorite_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import 'jellyfin_api_helper.dart';

part 'favorite_provider.g.dart';

/// All DefaultValues should be considered equal
/// All favoriteRequests with the same BaseItemDto id should be considered equal.
class FavoriteRequest {
final BaseItemDto? item;

Expand All @@ -33,8 +33,6 @@ class IsFavorite extends _$IsFavorite {
Future<void>? _initializing;

@override
// Because DefaultValue is always equal and we never invalidate, this should only
// be called once per itemId and all other DefaultValues should be ignored
bool build(FavoriteRequest value) {
if (value.item == null) {
return false;
Expand Down

0 comments on commit 13cc3f6

Please sign in to comment.