Implemented the saving and loading functionality of the queue from the server

This commit is contained in:
antonio 2023-03-13 22:55:56 +01:00
parent cbb6239b90
commit f5a3ba49cc
13 changed files with 223 additions and 16 deletions

View file

@ -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<PlayQueue> getPlayQueue() {
MutableLiveData<PlayQueue> playQueue = new MutableLiveData<>();
App.getSubsonicClientInstance(false)
.getBookmarksClient()
.getPlayQueue()
.enqueue(new Callback<ApiResponse>() {
@Override
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getPlayQueue() != null) {
playQueue.setValue(response.body().getSubsonicResponse().getPlayQueue());
}
}
@Override
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
playQueue.setValue(null);
}
});
return playQueue;
}
public void savePlayQueue(List<String> ids, String current, long position) {
App.getSubsonicClientInstance(false)
.getBookmarksClient()
.savePlayQueue(ids, current, position)
.enqueue(new Callback<ApiResponse>() {
@Override
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
}
@Override
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
}
});
}
public void insert(Child media, boolean reset, int afterIndex) {
try {
List<Queue> mediaList = new ArrayList<>();

View file

@ -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");

View file

@ -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<ApiResponse> getPlayQueue() {
Log.d(TAG, "getPlayQueue()");
return bookmarksService.getPlayQueue(subsonic.getParams());
}
public Call<ApiResponse> savePlayQueue(List<String> ids, String current, long position) {
Log.d(TAG, "savePlayQueue()");
return bookmarksService.savePlayQueue(subsonic.getParams(), ids, current, position);
}
}

View file

@ -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<ApiResponse> getPlayQueue(@QueryMap Map<String, String> params);
@GET("savePlayQueue")
Call<ApiResponse> savePlayQueue(@QueryMap Map<String, String> params, @Query("id") List<String> ids, @Query("current") String current, @Query("position") long position);
}

View file

@ -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<Child>? = 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
}

View file

@ -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<PlayQueue>() {
@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);
}
}

View file

@ -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();
}

View file

@ -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<PlayQueue> getPlayQueue() {
return queueRepository.getPlayQueue();
}
public boolean savePlayQueue() {
Child media = getLiveMedia().getValue();
List<Child> queue = queueRepository.getMedia();
List<String> 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);
}
}

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@color/titleTextColor"
android:pathData="M5,21V5Q5,4.175 5.588,3.587Q6.175,3 7,3H17Q17.825,3 18.413,3.587Q19,4.175 19,5V21L12,18Z" />
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@color/titleTextColor"
android:pathData="M4,20V18H6.725Q5.45,16.9 4.725,15.35Q4,13.8 4,12Q4,9.2 5.7,7.062Q7.4,4.925 10,4.25V6.35Q8.25,6.975 7.125,8.512Q6,10.05 6,12Q6,13.35 6.537,14.488Q7.075,15.625 8,16.45V14H10V20ZM15,20Q13.75,20 12.875,19.125Q12,18.25 12,17Q12,15.8 12.825,14.938Q13.65,14.075 14.85,14.025Q15.275,13.125 16.113,12.562Q16.95,12 18,12Q19.325,12 20.288,12.863Q21.25,13.725 21.45,15Q22.5,15 23.25,15.725Q24,16.45 24,17.475Q24,18.525 23.275,19.262Q22.55,20 21.5,20ZM17.9,11Q17.725,9.975 17.225,9.1Q16.725,8.225 16,7.55V10H14V4H20V6H17.275Q18.35,6.95 19.038,8.225Q19.725,9.5 19.925,11Z" />
</vector>

View file

@ -60,6 +60,7 @@
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="28dp"
android:visibility="gone">
<androidx.constraintlayout.widget.ConstraintLayout

View file

@ -98,7 +98,7 @@
android:insetRight="0dp"
android:insetBottom="0dp"
app:cornerRadius="30dp"
app:icon="@drawable/ic_lyrics"
app:icon="@drawable/ic_bookmark"
app:layout_constraintStart_toEndOf="@+id/vertical_guideline"
app:layout_constraintTop_toBottomOf="@+id/horizontal_guideline" />

View file

@ -63,6 +63,18 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageButton
android:id="@+id/player_header_bookmark_media_button"
android:layout_width="28dp"
android:layout_height="28dp"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:background="@drawable/ic_bookmark_sync"
android:foreground="?android:attr/selectableItemBackgroundBorderless"
app:layout_constraintBottom_toTopOf="@+id/player_header_seek_bar"
app:layout_constraintEnd_toStartOf="@+id/player_header_button"
app:layout_constraintTop_toTopOf="parent" />
<ImageButton
android:id="@+id/player_header_rewind_media_button"
android:layout_width="28dp"