From f5a3ba49cc8f4db089998e1f3be3e93c71220f64 Mon Sep 17 00:00:00 2001 From: antonio Date: Mon, 13 Mar 2023 22:55:56 +0100 Subject: [PATCH] Implemented the saving and loading functionality of the queue from the server --- .../play/repository/QueueRepository.java | 52 +++++++++++++++++++ .../play/subsonic/Subsonic.java | 9 ++++ .../api/bookmarks/BookmarksClient.java | 33 ++++++++++++ .../api/bookmarks/BookmarksService.java | 19 +++++++ .../play/subsonic/models/PlayQueue.kt | 8 +-- .../fragment/PlayerBottomSheetFragment.java | 37 +++++++++++++ .../play/ui/fragment/PlayerCoverFragment.java | 12 ++--- .../viewmodel/PlayerBottomSheetViewModel.java | 36 +++++++++++-- app/src/main/res/drawable/ic_bookmark.xml | 9 ++++ .../main/res/drawable/ic_bookmark_sync.xml | 9 ++++ app/src/main/res/layout/fragment_home.xml | 1 + .../layout/inner_fragment_player_cover.xml | 2 +- .../res/layout/player_header_bottom_sheet.xml | 12 +++++ 13 files changed, 223 insertions(+), 16 deletions(-) create mode 100644 app/src/main/java/com/cappielloantonio/play/subsonic/api/bookmarks/BookmarksClient.java create mode 100644 app/src/main/java/com/cappielloantonio/play/subsonic/api/bookmarks/BookmarksService.java create mode 100644 app/src/main/res/drawable/ic_bookmark.xml create mode 100644 app/src/main/res/drawable/ic_bookmark_sync.xml diff --git a/app/src/main/java/com/cappielloantonio/play/repository/QueueRepository.java b/app/src/main/java/com/cappielloantonio/play/repository/QueueRepository.java index 79172dca..63b57234 100644 --- a/app/src/main/java/com/cappielloantonio/play/repository/QueueRepository.java +++ b/app/src/main/java/com/cappielloantonio/play/repository/QueueRepository.java @@ -1,18 +1,30 @@ package com.cappielloantonio.play.repository; +import android.util.Log; + +import androidx.annotation.NonNull; import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; import androidx.media3.common.MediaItem; +import com.cappielloantonio.play.App; import com.cappielloantonio.play.database.AppDatabase; import com.cappielloantonio.play.database.dao.QueueDao; import com.cappielloantonio.play.model.Queue; +import com.cappielloantonio.play.subsonic.base.ApiResponse; import com.cappielloantonio.play.subsonic.models.Child; +import com.cappielloantonio.play.subsonic.models.PlayQueue; import java.time.Instant; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.stream.Collectors; +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + public class QueueRepository { private static final String TAG = "QueueRepository"; @@ -42,6 +54,46 @@ public class QueueRepository { return media; } + public MutableLiveData getPlayQueue() { + MutableLiveData playQueue = new MutableLiveData<>(); + + App.getSubsonicClientInstance(false) + .getBookmarksClient() + .getPlayQueue() + .enqueue(new Callback() { + @Override + public void onResponse(@NonNull Call call, @NonNull Response response) { + if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getPlayQueue() != null) { + playQueue.setValue(response.body().getSubsonicResponse().getPlayQueue()); + } + } + + @Override + public void onFailure(@NonNull Call call, @NonNull Throwable t) { + playQueue.setValue(null); + } + }); + + return playQueue; + } + + public void savePlayQueue(List ids, String current, long position) { + App.getSubsonicClientInstance(false) + .getBookmarksClient() + .savePlayQueue(ids, current, position) + .enqueue(new Callback() { + @Override + public void onResponse(@NonNull Call call, @NonNull Response response) { + + } + + @Override + public void onFailure(@NonNull Call call, @NonNull Throwable t) { + + } + }); + } + public void insert(Child media, boolean reset, int afterIndex) { try { List mediaList = new ArrayList<>(); diff --git a/app/src/main/java/com/cappielloantonio/play/subsonic/Subsonic.java b/app/src/main/java/com/cappielloantonio/play/subsonic/Subsonic.java index 1d6e8477..6eeebc7f 100644 --- a/app/src/main/java/com/cappielloantonio/play/subsonic/Subsonic.java +++ b/app/src/main/java/com/cappielloantonio/play/subsonic/Subsonic.java @@ -1,6 +1,7 @@ package com.cappielloantonio.play.subsonic; import com.cappielloantonio.play.subsonic.api.albumsonglist.AlbumSongListClient; +import com.cappielloantonio.play.subsonic.api.bookmarks.BookmarksClient; import com.cappielloantonio.play.subsonic.api.browsing.BrowsingClient; import com.cappielloantonio.play.subsonic.api.mediaannotation.MediaAnnotationClient; import com.cappielloantonio.play.subsonic.api.medialibraryscanning.MediaLibraryScanningClient; @@ -29,6 +30,7 @@ public class Subsonic { private MediaAnnotationClient mediaAnnotationClient; private PodcastClient podcastClient; private MediaLibraryScanningClient mediaLibraryScanningClient; + private BookmarksClient bookmarksClient; public Subsonic(SubsonicPreferences preferences) { this.preferences = preferences; @@ -101,6 +103,13 @@ public class Subsonic { return mediaLibraryScanningClient; } + public BookmarksClient getBookmarksClient() { + if (bookmarksClient == null) { + bookmarksClient = new BookmarksClient(this); + } + return bookmarksClient; + } + public String getUrl() { String url = preferences.getServerUrl() + "/rest/"; return url.replace("//rest", "/rest"); diff --git a/app/src/main/java/com/cappielloantonio/play/subsonic/api/bookmarks/BookmarksClient.java b/app/src/main/java/com/cappielloantonio/play/subsonic/api/bookmarks/BookmarksClient.java new file mode 100644 index 00000000..f0aaba12 --- /dev/null +++ b/app/src/main/java/com/cappielloantonio/play/subsonic/api/bookmarks/BookmarksClient.java @@ -0,0 +1,33 @@ +package com.cappielloantonio.play.subsonic.api.bookmarks; + +import android.util.Log; + +import com.cappielloantonio.play.subsonic.RetrofitClient; +import com.cappielloantonio.play.subsonic.Subsonic; +import com.cappielloantonio.play.subsonic.base.ApiResponse; + +import java.util.List; + +import retrofit2.Call; + +public class BookmarksClient { + private static final String TAG = "BookmarksClient"; + + private final Subsonic subsonic; + private final BookmarksService bookmarksService; + + public BookmarksClient(Subsonic subsonic) { + this.subsonic = subsonic; + this.bookmarksService = new RetrofitClient(subsonic).getRetrofit().create(BookmarksService.class); + } + + public Call getPlayQueue() { + Log.d(TAG, "getPlayQueue()"); + return bookmarksService.getPlayQueue(subsonic.getParams()); + } + + public Call savePlayQueue(List ids, String current, long position) { + Log.d(TAG, "savePlayQueue()"); + return bookmarksService.savePlayQueue(subsonic.getParams(), ids, current, position); + } +} diff --git a/app/src/main/java/com/cappielloantonio/play/subsonic/api/bookmarks/BookmarksService.java b/app/src/main/java/com/cappielloantonio/play/subsonic/api/bookmarks/BookmarksService.java new file mode 100644 index 00000000..5326af5a --- /dev/null +++ b/app/src/main/java/com/cappielloantonio/play/subsonic/api/bookmarks/BookmarksService.java @@ -0,0 +1,19 @@ +package com.cappielloantonio.play.subsonic.api.bookmarks; + +import com.cappielloantonio.play.subsonic.base.ApiResponse; + +import java.util.List; +import java.util.Map; + +import retrofit2.Call; +import retrofit2.http.GET; +import retrofit2.http.Query; +import retrofit2.http.QueryMap; + +public interface BookmarksService { + @GET("getPlayQueue") + Call getPlayQueue(@QueryMap Map params); + + @GET("savePlayQueue") + Call savePlayQueue(@QueryMap Map params, @Query("id") List ids, @Query("current") String current, @Query("position") long position); +} diff --git a/app/src/main/java/com/cappielloantonio/play/subsonic/models/PlayQueue.kt b/app/src/main/java/com/cappielloantonio/play/subsonic/models/PlayQueue.kt index b5abcce4..0ecee9e1 100644 --- a/app/src/main/java/com/cappielloantonio/play/subsonic/models/PlayQueue.kt +++ b/app/src/main/java/com/cappielloantonio/play/subsonic/models/PlayQueue.kt @@ -1,12 +1,14 @@ package com.cappielloantonio.play.subsonic.models -import java.time.LocalDateTime +import com.google.gson.annotations.SerializedName +import java.util.* class PlayQueue { + @SerializedName("entry") var entries: List? = null - var current: Int? = null + var current: String? = null var position: Long? = null var username: String? = null - var changed: LocalDateTime? = null + var changed: Date? = null var changedBy: String? = null } \ No newline at end of file diff --git a/app/src/main/java/com/cappielloantonio/play/ui/fragment/PlayerBottomSheetFragment.java b/app/src/main/java/com/cappielloantonio/play/ui/fragment/PlayerBottomSheetFragment.java index 30232ba9..249b0062 100644 --- a/app/src/main/java/com/cappielloantonio/play/ui/fragment/PlayerBottomSheetFragment.java +++ b/app/src/main/java/com/cappielloantonio/play/ui/fragment/PlayerBottomSheetFragment.java @@ -11,6 +11,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.OptIn; import androidx.fragment.app.Fragment; +import androidx.lifecycle.Observer; import androidx.lifecycle.ViewModelProvider; import androidx.media3.common.MediaMetadata; import androidx.media3.common.Player; @@ -26,7 +27,9 @@ import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions; import com.cappielloantonio.play.R; import com.cappielloantonio.play.databinding.FragmentPlayerBottomSheetBinding; import com.cappielloantonio.play.glide.CustomGlideRequest; +import com.cappielloantonio.play.service.MediaManager; import com.cappielloantonio.play.service.MediaService; +import com.cappielloantonio.play.subsonic.models.PlayQueue; import com.cappielloantonio.play.ui.fragment.pager.PlayerControllerVerticalPager; import com.cappielloantonio.play.util.Constants; import com.cappielloantonio.play.util.MusicUtil; @@ -35,6 +38,8 @@ import com.google.android.material.elevation.SurfaceColors; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; +import java.util.stream.IntStream; + @OptIn(markerClass = UnstableApi.class) public class PlayerBottomSheetFragment extends Fragment { private FragmentPlayerBottomSheetBinding bind; @@ -55,6 +60,7 @@ public class PlayerBottomSheetFragment extends Fragment { customizeBottomSheetBackground(); initViewPager(); + setHeaderBookmarksButton(); return view; } @@ -247,4 +253,35 @@ public class PlayerBottomSheetFragment extends Fragment { progressBarHandler.removeCallbacks(progressBarRunnable); } } + + private void setHeaderBookmarksButton() { + playerBottomSheetViewModel.getPlayQueue().observeForever(new Observer() { + @Override + public void onChanged(PlayQueue playQueue) { + if (playQueue != null && !playQueue.getEntries().isEmpty()) { + int index = IntStream.range(0, playQueue.getEntries().size()).filter(ix -> playQueue.getEntries().get(ix).getId().equals(playQueue.getCurrent())).findFirst().orElse(-1); + + if (index != -1) { + bind.playerHeaderLayout.playerHeaderBookmarkMediaButton.setVisibility(View.VISIBLE); + bind.playerHeaderLayout.playerHeaderBookmarkMediaButton.setOnClickListener(v -> { + MediaManager.startQueue(mediaBrowserListenableFuture, playQueue.getEntries(), index); + bind.playerHeaderLayout.playerHeaderBookmarkMediaButton.setVisibility(View.GONE); + }); + } + } else { + bind.playerHeaderLayout.playerHeaderBookmarkMediaButton.setVisibility(View.GONE); + bind.playerHeaderLayout.playerHeaderBookmarkMediaButton.setOnClickListener(null); + } + + playerBottomSheetViewModel.getPlayQueue().removeObserver(this); + } + }); + + bind.playerHeaderLayout.playerHeaderBookmarkMediaButton.setOnLongClickListener(v -> { + bind.playerHeaderLayout.playerHeaderBookmarkMediaButton.setVisibility(View.GONE); + return false; + }); + + new Handler().postDelayed(() -> bind.playerHeaderLayout.playerHeaderBookmarkMediaButton.setVisibility(View.GONE), 5000); + } } diff --git a/app/src/main/java/com/cappielloantonio/play/ui/fragment/PlayerCoverFragment.java b/app/src/main/java/com/cappielloantonio/play/ui/fragment/PlayerCoverFragment.java index 4cb6a5eb..ced493c5 100644 --- a/app/src/main/java/com/cappielloantonio/play/ui/fragment/PlayerCoverFragment.java +++ b/app/src/main/java/com/cappielloantonio/play/ui/fragment/PlayerCoverFragment.java @@ -31,6 +31,7 @@ import com.cappielloantonio.play.util.Constants; import com.cappielloantonio.play.util.DownloadUtil; import com.cappielloantonio.play.util.MappingUtil; import com.cappielloantonio.play.viewmodel.PlayerBottomSheetViewModel; +import com.google.android.material.snackbar.Snackbar; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; @@ -114,11 +115,8 @@ public class PlayerCoverFragment extends Fragment { }); bind.innerButtonBottomRight.setOnClickListener(view -> { - if (getActivity() != null) { - PlayerBottomSheetFragment playerBottomSheetFragment = (PlayerBottomSheetFragment) requireActivity().getSupportFragmentManager().findFragmentByTag("PlayerBottomSheet"); - if (playerBottomSheetFragment != null) { - playerBottomSheetFragment.goToLyricsPage(); - } + if (playerBottomSheetViewModel.savePlayQueue()) { + Snackbar.make(requireView(), "Salvato", Snackbar.LENGTH_LONG).show(); } }); } @@ -136,8 +134,8 @@ public class PlayerCoverFragment extends Fragment { private void bindMediaController() { mediaBrowserListenableFuture.addListener(() -> { try { - MediaBrowser mediaBrowseri = mediaBrowserListenableFuture.get(); - setMediaBrowserListener(mediaBrowseri); + MediaBrowser mediaBrowser = mediaBrowserListenableFuture.get(); + setMediaBrowserListener(mediaBrowser); } catch (Exception exception) { exception.printStackTrace(); } diff --git a/app/src/main/java/com/cappielloantonio/play/viewmodel/PlayerBottomSheetViewModel.java b/app/src/main/java/com/cappielloantonio/play/viewmodel/PlayerBottomSheetViewModel.java index b949dbbe..fb280bfd 100644 --- a/app/src/main/java/com/cappielloantonio/play/viewmodel/PlayerBottomSheetViewModel.java +++ b/app/src/main/java/com/cappielloantonio/play/viewmodel/PlayerBottomSheetViewModel.java @@ -11,17 +11,23 @@ import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; import androidx.media3.common.util.UnstableApi; +import com.cappielloantonio.play.model.Download; import com.cappielloantonio.play.model.Queue; import com.cappielloantonio.play.repository.ArtistRepository; import com.cappielloantonio.play.repository.QueueRepository; import com.cappielloantonio.play.repository.SongRepository; import com.cappielloantonio.play.subsonic.models.ArtistID3; import com.cappielloantonio.play.subsonic.models.Child; +import com.cappielloantonio.play.subsonic.models.PlayQueue; import com.cappielloantonio.play.util.Constants; +import com.cappielloantonio.play.util.DownloadUtil; +import com.cappielloantonio.play.util.MappingUtil; +import com.cappielloantonio.play.util.Preferences; import java.util.Collections; import java.util.Date; import java.util.List; +import java.util.stream.Collectors; @OptIn(markerClass = UnstableApi.class) public class PlayerBottomSheetViewModel extends AndroidViewModel { @@ -59,13 +65,12 @@ public class PlayerBottomSheetViewModel extends AndroidViewModel { songRepository.star(media.getId()); media.setStarred(new Date()); - // TODO - /* if (Preferences.isStarredSyncEnabled()) { + if (Preferences.isStarredSyncEnabled()) { DownloadUtil.getDownloadTracker(context).download( - MappingUtil.mapMediaItem(media, false), - MappingUtil.mapDownload(media, null, null) + MappingUtil.mapDownload(media), + new Download(media) ); - } */ + } } } } @@ -119,4 +124,25 @@ public class PlayerBottomSheetViewModel extends AndroidViewModel { return instantMix; } + + public LiveData getPlayQueue() { + return queueRepository.getPlayQueue(); + } + + public boolean savePlayQueue() { + Child media = getLiveMedia().getValue(); + List queue = queueRepository.getMedia(); + List ids = queue.stream().map(Child::getId).collect(Collectors.toList()); + + if (media != null) { + queueRepository.savePlayQueue(ids, media.getId(), 0); + return true; + } + + return false; + } + + public void emptyPlayQueue() { + queueRepository.savePlayQueue(null, null, 0); + } } diff --git a/app/src/main/res/drawable/ic_bookmark.xml b/app/src/main/res/drawable/ic_bookmark.xml new file mode 100644 index 00000000..c2042b00 --- /dev/null +++ b/app/src/main/res/drawable/ic_bookmark.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_bookmark_sync.xml b/app/src/main/res/drawable/ic_bookmark_sync.xml new file mode 100644 index 00000000..b27f3a19 --- /dev/null +++ b/app/src/main/res/drawable/ic_bookmark_sync.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml index 9693d048..2c8c9b5a 100644 --- a/app/src/main/res/layout/fragment_home.xml +++ b/app/src/main/res/layout/fragment_home.xml @@ -60,6 +60,7 @@ android:layout_height="wrap_content" android:layout_marginHorizontal="16dp" android:layout_marginTop="8dp" + android:layout_marginBottom="28dp" android:visibility="gone"> diff --git a/app/src/main/res/layout/player_header_bottom_sheet.xml b/app/src/main/res/layout/player_header_bottom_sheet.xml index 31697b36..a30449ce 100644 --- a/app/src/main/res/layout/player_header_bottom_sheet.xml +++ b/app/src/main/res/layout/player_header_bottom_sheet.xml @@ -63,6 +63,18 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" /> + +