mirror of
https://github.com/antebudimir/tempus.git
synced 2026-04-15 16:27:26 +00:00
Merge branch 'development' into improve/update-zh
This commit is contained in:
commit
53ca88989f
19 changed files with 681 additions and 296 deletions
18
USAGE.md
18
USAGE.md
|
|
@ -160,7 +160,23 @@ If your server supports it - add a internet radio station feed
|
|||
## Android Auto
|
||||
|
||||
### Enabling on your head unit
|
||||
- You have to enable Android Auto developer options, which are different from actual Android dev options. Then you have to enable "Unknown sources" in Android Auto, otherwise the app won't appear as it isn't downloaded from Play Store. (screenshots needed)
|
||||
To allow the Tempus app on your car's head unit, "Unknown sources" needs to be enabled in the Android Auto "Developer settings". This is because Tempus isn't installed through Play Store. Note that the Android Auto developer settings are different from the global Android "Developer options".
|
||||
1. Switch to developer mode in the Android Auto settings by tapping ten times on the "Version" item at the bottom, followed by giving your permission.
|
||||
<p align="left">
|
||||
<img width="270" height="600" alt="1a" src="https://github.com/user-attachments/assets/f09f6999-9761-4b05-8ec7-bf221a15dda3" />
|
||||
<img width="270" height="600" alt="1b" src="https://github.com/user-attachments/assets/0795e508-ba01-41c5-96a7-7c03b0156591" />
|
||||
<img width="270" height="600" alt="1c" src="https://github.com/user-attachments/assets/51c15f67-fddb-452e-b5d3-5092edeab390" />
|
||||
</p>
|
||||
|
||||
2. Go to the "Developer settings" by the menu at the top right.
|
||||
<p align="left">
|
||||
<img width="270" height="600" alt="2" src="https://github.com/user-attachments/assets/1ecd1f3e-026d-4d25-87f2-be7f12efbac6" />
|
||||
</p>
|
||||
|
||||
3. Scroll down to the bottom and check "Unknown sources".
|
||||
<p align="left">
|
||||
<img width="270" height="600" alt="3" src="https://github.com/user-attachments/assets/37db88e9-1b76-417f-9c47-da9f3a750fff" />
|
||||
</p>
|
||||
|
||||
|
||||
### Server Settings
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ android {
|
|||
targetSdk 35
|
||||
|
||||
versionCode 11
|
||||
versionName '4.6.1'
|
||||
versionName '4.6.2'
|
||||
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
||||
|
||||
javaCompileOptions {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.cappielloantonio.tempo.repository;
|
|||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.cappielloantonio.tempo.App;
|
||||
|
|
@ -11,6 +12,7 @@ import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
|
|||
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
|
||||
import com.cappielloantonio.tempo.subsonic.models.AlbumInfo;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||
import com.cappielloantonio.tempo.util.Constants.SeedType;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
|
|
@ -204,38 +206,15 @@ public class AlbumRepository {
|
|||
return albumInfo;
|
||||
}
|
||||
|
||||
public void getInstantMix(AlbumID3 album, int count, MediaCallback callback) {
|
||||
Log.d("AlbumRepository", "Attempting getInstantMix for AlbumID: " + album.getId());
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getBrowsingClient()
|
||||
.getSimilarSongs2(album.getId(), count)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
List<Child> songs = new ArrayList<>();
|
||||
public void getInstantMix(AlbumID3 album, int count, final MediaCallback callback) {
|
||||
Log.d("AlbumRepository", "Starting Instant Mix for album: " + album.getName());
|
||||
|
||||
if (response.isSuccessful()
|
||||
&& response.body() != null
|
||||
&& response.body().getSubsonicResponse().getSimilarSongs2() != null) {
|
||||
|
||||
List<Child> similarSongs = response.body().getSubsonicResponse().getSimilarSongs2().getSongs();
|
||||
|
||||
if (similarSongs == null) {
|
||||
Log.w("AlbumRepository", "API successful but 'songs' list was NULL for AlbumID: " + album.getId());
|
||||
} else {
|
||||
songs.addAll(similarSongs);
|
||||
}
|
||||
}
|
||||
new SongRepository().getInstantMix(album.getId(), SeedType.ALBUM, count, songs -> {
|
||||
|
||||
callback.onLoadMedia(songs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
callback.onLoadMedia(new ArrayList<>());
|
||||
}
|
||||
});
|
||||
if (songs != null && !songs.isEmpty()) {
|
||||
callback.onLoadMedia(songs);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public MutableLiveData<List<Integer>> getDecades() {
|
||||
|
|
@ -248,7 +227,7 @@ public class AlbumRepository {
|
|||
@Override
|
||||
public void onLoadYear(int last) {
|
||||
if (first != -1 && last != -1) {
|
||||
List<Integer> decadeList = new ArrayList();
|
||||
List<Integer> decadeList = new ArrayList<>();
|
||||
|
||||
int startDecade = first - (first % 10);
|
||||
int lastDecade = last - (last % 10);
|
||||
|
|
|
|||
|
|
@ -5,12 +5,14 @@ import androidx.lifecycle.MutableLiveData;
|
|||
import android.util.Log;
|
||||
|
||||
import com.cappielloantonio.tempo.App;
|
||||
import com.cappielloantonio.tempo.interfaces.MediaCallback;
|
||||
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
|
||||
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
|
||||
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
|
||||
import com.cappielloantonio.tempo.subsonic.models.ArtistInfo2;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||
import com.cappielloantonio.tempo.subsonic.models.IndexID3;
|
||||
import com.cappielloantonio.tempo.util.Constants.SeedType;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
|
@ -149,7 +151,7 @@ public class ArtistRepository {
|
|||
|
||||
if(response.body().getSubsonicResponse().getArtists() != null && response.body().getSubsonicResponse().getArtists().getIndices() != null) {
|
||||
for (IndexID3 index : response.body().getSubsonicResponse().getArtists().getIndices()) {
|
||||
if(index != null && index.getArtists() != null) {
|
||||
if(index.getArtists() != null) {
|
||||
artists.addAll(index.getArtists());
|
||||
}
|
||||
}
|
||||
|
|
@ -287,28 +289,22 @@ public class ArtistRepository {
|
|||
}
|
||||
|
||||
public MutableLiveData<List<Child>> getInstantMix(ArtistID3 artist, int count) {
|
||||
MutableLiveData<List<Child>> instantMix = new MutableLiveData<>();
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getBrowsingClient()
|
||||
.getSimilarSongs2(artist.getId(), count)
|
||||
.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().getSimilarSongs2() != null) {
|
||||
instantMix.setValue(response.body().getSubsonicResponse().getSimilarSongs2().getSongs());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
return instantMix;
|
||||
// Delegate to the centralized SongRepository
|
||||
return new SongRepository().getInstantMix(artist.getId(), SeedType.ARTIST, count);
|
||||
}
|
||||
|
||||
public void getInstantMix(ArtistID3 artist, int count, final MediaCallback callback) {
|
||||
// Delegate to the centralized SongRepository
|
||||
new SongRepository().getInstantMix(artist.getId(), SeedType.ARTIST, count, songs -> {
|
||||
if (songs != null && !songs.isEmpty()) {
|
||||
callback.onLoadMedia(songs);
|
||||
} else {
|
||||
callback.onLoadMedia(Collections.emptyList());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public MutableLiveData<List<Child>> getRandomSong(ArtistID3 artist, int count) {
|
||||
MutableLiveData<List<Child>> randomSongs = new MutableLiveData<>();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,22 +1,33 @@
|
|||
package com.cappielloantonio.tempo.repository;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import com.cappielloantonio.tempo.App;
|
||||
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||
import com.cappielloantonio.tempo.subsonic.models.SubsonicResponse;
|
||||
import com.cappielloantonio.tempo.util.Constants.SeedType;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
import retrofit2.Response;
|
||||
|
||||
public class SongRepository {
|
||||
|
||||
private static final String TAG = "SongRepository";
|
||||
public interface MediaCallbackInternal {
|
||||
void onSongsAvailable(List<Child> songs);
|
||||
}
|
||||
|
||||
public MutableLiveData<List<Child>> getStarredSongs(boolean random, int size) {
|
||||
MutableLiveData<List<Child>> starredSongs = new MutableLiveData<>(Collections.emptyList());
|
||||
|
|
@ -42,219 +53,332 @@ public class SongRepository {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {}
|
||||
});
|
||||
|
||||
return starredSongs;
|
||||
}
|
||||
|
||||
public MutableLiveData<List<Child>> getInstantMix(String id, int count) {
|
||||
MutableLiveData<List<Child>> instantMix = new MutableLiveData<>();
|
||||
/**
|
||||
* Used by ViewModels. Updates the LiveData list incrementally as songs are found.
|
||||
*/
|
||||
public MutableLiveData<List<Child>> getInstantMix(String id, SeedType type, int count) {
|
||||
MutableLiveData<List<Child>> instantMix = new MutableLiveData<>(new ArrayList<>());
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getBrowsingClient()
|
||||
.getSimilarSongs2(id, count)
|
||||
.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().getSimilarSongs2() != null) {
|
||||
instantMix.setValue(response.body().getSubsonicResponse().getSimilarSongs2().getSongs());
|
||||
performSmartMix(id, type, count, songs -> {
|
||||
List<Child> current = instantMix.getValue();
|
||||
if (current != null) {
|
||||
for (Child s : songs) {
|
||||
if (!current.contains(s)) current.add(s);
|
||||
}
|
||||
|
||||
if (current.size() < count / 2) {
|
||||
fillWithRandom(count - current.size(), remainder -> {
|
||||
for (Child r : remainder) {
|
||||
if (!current.contains(r)) current.add(r);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
instantMix.setValue(null);
|
||||
}
|
||||
});
|
||||
instantMix.postValue(current);
|
||||
});
|
||||
} else {
|
||||
instantMix.postValue(current);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return instantMix;
|
||||
}
|
||||
|
||||
public MutableLiveData<List<Child>> getRandomSample(int number, Integer fromYear, Integer toYear) {
|
||||
MutableLiveData<List<Child>> randomSongsSample = new MutableLiveData<>();
|
||||
/**
|
||||
* Overloaded method used by other Repositories
|
||||
*/
|
||||
public void getInstantMix(String id, SeedType type, int count, MediaCallbackInternal callback) {
|
||||
new MediaCallbackAccumulator(callback, count).start(id, type);
|
||||
}
|
||||
|
||||
private class MediaCallbackAccumulator {
|
||||
private final MediaCallbackInternal originalCallback;
|
||||
private final int targetCount;
|
||||
private final List<Child> accumulatedSongs = new ArrayList<>();
|
||||
private final Set<String> trackIds = new HashSet<>();
|
||||
private boolean isComplete = false;
|
||||
|
||||
MediaCallbackAccumulator(MediaCallbackInternal callback, int count) {
|
||||
this.originalCallback = callback;
|
||||
this.targetCount = count;
|
||||
}
|
||||
|
||||
void start(String id, SeedType type) {
|
||||
performSmartMix(id, type, targetCount, this::onBatchReceived);
|
||||
}
|
||||
|
||||
private void onBatchReceived(List<Child> batch) {
|
||||
if (isComplete || batch == null || batch.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int added = 0;
|
||||
for (Child song : batch) {
|
||||
if (!trackIds.contains(song.getId()) && accumulatedSongs.size() < targetCount) {
|
||||
trackIds.add(song.getId());
|
||||
accumulatedSongs.add(song);
|
||||
added++;
|
||||
}
|
||||
}
|
||||
|
||||
if (accumulatedSongs.size() >= targetCount) {
|
||||
originalCallback.onSongsAvailable(new ArrayList<>(accumulatedSongs));
|
||||
isComplete = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void performSmartMix(final String id, final SeedType type, final int count, final MediaCallbackInternal callback) {
|
||||
switch (type) {
|
||||
case ARTIST:
|
||||
fetchSimilarByArtist(id, count, callback);
|
||||
break;
|
||||
case ALBUM:
|
||||
fetchAlbumSongsThenSimilar(id, count, callback);
|
||||
break;
|
||||
case TRACK:
|
||||
fetchSingleTrackThenSimilar(id, count, callback);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void fetchAlbumSongsThenSimilar(String albumId, int count, MediaCallbackInternal callback) {
|
||||
App.getSubsonicClientInstance(false).getBrowsingClient().getAlbum(albumId).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().getAlbum() != null) {
|
||||
List<Child> albumSongs = response.body().getSubsonicResponse().getAlbum().getSongs();
|
||||
if (albumSongs != null && !albumSongs.isEmpty()) {
|
||||
int fromAlbum = Math.min(count, albumSongs.size());
|
||||
List<Child> limitedAlbumSongs = albumSongs.subList(0, fromAlbum);
|
||||
callback.onSongsAvailable(new ArrayList<>(limitedAlbumSongs));
|
||||
|
||||
int remaining = count - fromAlbum;
|
||||
if (remaining > 0 && albumSongs.get(0).getArtistId() != null) {
|
||||
fetchSimilarByArtist(albumSongs.get(0).getArtistId(), remaining, callback);
|
||||
} else if (remaining > 0) {
|
||||
Log.d(TAG, "No artistId available, skipping similar artist fetch");
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Log.d(TAG, "Album fetch failed or empty, calling fillWithRandom");
|
||||
fillWithRandom(count, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
Log.d(TAG, "Album fetch failed: " + t.getMessage());
|
||||
fillWithRandom(count, callback);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void fetchSimilarByArtist(String artistId, final int count, final MediaCallbackInternal callback) {
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getAlbumSongListClient()
|
||||
.getRandomSongs(number, fromYear, toYear)
|
||||
.getBrowsingClient()
|
||||
.getSimilarSongs2(artistId, count)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
List<Child> songs = new ArrayList<>();
|
||||
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getRandomSongs() != null && response.body().getSubsonicResponse().getRandomSongs().getSongs() != null) {
|
||||
songs.addAll(response.body().getSubsonicResponse().getRandomSongs().getSongs());
|
||||
List<Child> similar = extractSongs(response, "similarSongs2");
|
||||
if (!similar.isEmpty()) {
|
||||
List<Child> limitedSimilar = similar.subList(0, Math.min(count, similar.size()));
|
||||
callback.onSongsAvailable(limitedSimilar);
|
||||
} else {
|
||||
fillWithRandom(count, callback);
|
||||
}
|
||||
|
||||
randomSongsSample.setValue(songs);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
fillWithRandom(count, callback);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void fetchSingleTrackThenSimilar(String trackId, int count, MediaCallbackInternal callback) {
|
||||
App.getSubsonicClientInstance(false).getBrowsingClient().getSong(trackId).enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null) {
|
||||
Child song = response.body().getSubsonicResponse().getSong();
|
||||
if (song != null) {
|
||||
callback.onSongsAvailable(Collections.singletonList(song));
|
||||
int remaining = count - 1;
|
||||
if (remaining > 0) {
|
||||
fetchSimilarOnly(trackId, remaining, callback);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
fillWithRandom(count, callback);
|
||||
}
|
||||
@Override public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
fillWithRandom(count, callback);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void fetchSimilarOnly(String id, int count, MediaCallbackInternal callback) {
|
||||
App.getSubsonicClientInstance(false).getBrowsingClient().getSimilarSongs(id, count).enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
List<Child> songs = extractSongs(response, "similarSongs");
|
||||
if (!songs.isEmpty()) {
|
||||
List<Child> limitedSongs = songs.subList(0, Math.min(count, songs.size()));
|
||||
callback.onSongsAvailable(limitedSongs);
|
||||
} else {
|
||||
fillWithRandom(count, callback);
|
||||
}
|
||||
}
|
||||
@Override public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
fillWithRandom(count, callback);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private void fillWithRandom(int target, final MediaCallbackInternal callback) {
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getAlbumSongListClient()
|
||||
.getRandomSongs(target, null, null)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
List<Child> random = extractSongs(response, "randomSongs");
|
||||
if (!random.isEmpty()) {
|
||||
List<Child> limitedRandom = random.subList(0, Math.min(target, random.size()));
|
||||
callback.onSongsAvailable(limitedRandom);
|
||||
} else {
|
||||
callback.onSongsAvailable(new ArrayList<>());
|
||||
}
|
||||
}
|
||||
@Override public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
callback.onSongsAvailable(new ArrayList<>());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private List<Child> extractSongs(Response<ApiResponse> response, String type) {
|
||||
if (response.isSuccessful() && response.body() != null) {
|
||||
SubsonicResponse res = response.body().getSubsonicResponse();
|
||||
List<Child> list = null;
|
||||
if (type.equals("similarSongs") && res.getSimilarSongs() != null) {
|
||||
list = res.getSimilarSongs().getSongs();
|
||||
} else if (type.equals("similarSongs2") && res.getSimilarSongs2() != null) {
|
||||
list = res.getSimilarSongs2().getSongs();
|
||||
} else if (type.equals("randomSongs") && res.getRandomSongs() != null) {
|
||||
list = res.getRandomSongs().getSongs();
|
||||
}
|
||||
return (list != null) ? list : new ArrayList<>();
|
||||
}
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
public MutableLiveData<List<Child>> getRandomSample(int number, Integer fromYear, Integer toYear) {
|
||||
MutableLiveData<List<Child>> randomSongsSample = new MutableLiveData<>();
|
||||
App.getSubsonicClientInstance(false).getAlbumSongListClient().getRandomSongs(number, fromYear, toYear).enqueue(new Callback<ApiResponse>() {
|
||||
@Override public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
List<Child> songs = new ArrayList<>();
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getRandomSongs() != null) {
|
||||
songs.addAll(Objects.requireNonNull(response.body().getSubsonicResponse().getRandomSongs().getSongs()));
|
||||
}
|
||||
randomSongsSample.setValue(songs);
|
||||
}
|
||||
@Override public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {}
|
||||
});
|
||||
return randomSongsSample;
|
||||
}
|
||||
|
||||
public MutableLiveData<List<Child>> getRandomSampleWithGenre(int number, Integer fromYear, Integer toYear, String genre) {
|
||||
MutableLiveData<List<Child>> randomSongsSample = new MutableLiveData<>();
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getAlbumSongListClient()
|
||||
.getRandomSongs(number, fromYear, toYear, genre)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
List<Child> songs = new ArrayList<>();
|
||||
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getRandomSongs() != null && response.body().getSubsonicResponse().getRandomSongs().getSongs() != null) {
|
||||
songs.addAll(response.body().getSubsonicResponse().getRandomSongs().getSongs());
|
||||
}
|
||||
|
||||
randomSongsSample.setValue(songs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
App.getSubsonicClientInstance(false).getAlbumSongListClient().getRandomSongs(number, fromYear, toYear, genre).enqueue(new Callback<ApiResponse>() {
|
||||
@Override public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
List<Child> songs = new ArrayList<>();
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getRandomSongs() != null) {
|
||||
songs.addAll(Objects.requireNonNull(response.body().getSubsonicResponse().getRandomSongs().getSongs()));
|
||||
}
|
||||
randomSongsSample.setValue(songs);
|
||||
}
|
||||
@Override public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {}
|
||||
});
|
||||
return randomSongsSample;
|
||||
}
|
||||
|
||||
public void scrobble(String id, boolean submission) {
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getMediaAnnotationClient()
|
||||
.scrobble(id, submission)
|
||||
.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) {
|
||||
|
||||
}
|
||||
});
|
||||
App.getSubsonicClientInstance(false).getMediaAnnotationClient().scrobble(id, submission).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 setRating(String id, int rating) {
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getMediaAnnotationClient()
|
||||
.setRating(id, rating)
|
||||
.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) {
|
||||
|
||||
}
|
||||
});
|
||||
App.getSubsonicClientInstance(false).getMediaAnnotationClient().setRating(id, rating).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 MutableLiveData<List<Child>> getSongsByGenre(String id, int page) {
|
||||
MutableLiveData<List<Child>> songsByGenre = new MutableLiveData<>();
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getAlbumSongListClient()
|
||||
.getSongsByGenre(id, 100, 100 * page)
|
||||
.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().getSongsByGenre() != null) {
|
||||
songsByGenre.setValue(response.body().getSubsonicResponse().getSongsByGenre().getSongs());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
App.getSubsonicClientInstance(false).getAlbumSongListClient().getSongsByGenre(id, 100, 100 * page).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().getSongsByGenre() != null) {
|
||||
songsByGenre.setValue(response.body().getSubsonicResponse().getSongsByGenre().getSongs());
|
||||
}
|
||||
}
|
||||
@Override public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {}
|
||||
});
|
||||
return songsByGenre;
|
||||
}
|
||||
|
||||
public MutableLiveData<List<Child>> getSongsByGenres(ArrayList<String> genresId) {
|
||||
MutableLiveData<List<Child>> songsByGenre = new MutableLiveData<>();
|
||||
|
||||
for (String id : genresId)
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getAlbumSongListClient()
|
||||
.getSongsByGenre(id, 500, 0)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
List<Child> songs = new ArrayList<>();
|
||||
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getSongsByGenre() != null) {
|
||||
songs.addAll(response.body().getSubsonicResponse().getSongsByGenre().getSongs());
|
||||
}
|
||||
|
||||
songsByGenre.setValue(songs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
for (String id : genresId) {
|
||||
App.getSubsonicClientInstance(false).getAlbumSongListClient().getSongsByGenre(id, 500, 0).enqueue(new Callback<ApiResponse>() {
|
||||
@Override public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
List<Child> songs = new ArrayList<>();
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getSongsByGenre() != null) {
|
||||
songs.addAll(Objects.requireNonNull(response.body().getSubsonicResponse().getSongsByGenre().getSongs()));
|
||||
}
|
||||
songsByGenre.setValue(songs);
|
||||
}
|
||||
@Override public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {}
|
||||
});
|
||||
}
|
||||
return songsByGenre;
|
||||
}
|
||||
|
||||
public MutableLiveData<Child> getSong(String id) {
|
||||
MutableLiveData<Child> song = new MutableLiveData<>();
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getBrowsingClient()
|
||||
.getSong(id)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null) {
|
||||
song.setValue(response.body().getSubsonicResponse().getSong());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
App.getSubsonicClientInstance(false).getBrowsingClient().getSong(id).enqueue(new Callback<ApiResponse>() {
|
||||
@Override public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null) {
|
||||
song.setValue(response.body().getSubsonicResponse().getSong());
|
||||
}
|
||||
}
|
||||
@Override public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {}
|
||||
});
|
||||
return song;
|
||||
}
|
||||
|
||||
public MutableLiveData<String> getSongLyrics(Child song) {
|
||||
MutableLiveData<String> lyrics = new MutableLiveData<>(null);
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getMediaRetrievalClient()
|
||||
.getLyrics(song.getArtist(), song.getTitle())
|
||||
.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().getLyrics() != null) {
|
||||
lyrics.setValue(response.body().getSubsonicResponse().getLyrics().getValue());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
App.getSubsonicClientInstance(false).getMediaRetrievalClient().getLyrics(song.getArtist(), song.getTitle()).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().getLyrics() != null) {
|
||||
lyrics.setValue(response.body().getSubsonicResponse().getLyrics().getValue());
|
||||
}
|
||||
}
|
||||
@Override public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {}
|
||||
});
|
||||
return lyrics;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -26,6 +26,7 @@ import com.cappielloantonio.tempo.repository.SongRepository;
|
|||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||
import com.cappielloantonio.tempo.subsonic.models.InternetRadioStation;
|
||||
import com.cappielloantonio.tempo.subsonic.models.PodcastEpisode;
|
||||
import com.cappielloantonio.tempo.util.Constants.SeedType;
|
||||
import com.cappielloantonio.tempo.util.MappingUtil;
|
||||
import com.cappielloantonio.tempo.util.Preferences;
|
||||
import com.cappielloantonio.tempo.viewmodel.PlaybackViewModel;
|
||||
|
|
@ -183,11 +184,13 @@ public class MediaManager {
|
|||
@OptIn(markerClass = UnstableApi.class)
|
||||
public static void startQueue(ListenableFuture<MediaBrowser> mediaBrowserListenableFuture, List<Child> media, int startIndex) {
|
||||
if (mediaBrowserListenableFuture != null) {
|
||||
|
||||
mediaBrowserListenableFuture.addListener(() -> {
|
||||
try {
|
||||
if (mediaBrowserListenableFuture.isDone()) {
|
||||
final MediaBrowser browser = mediaBrowserListenableFuture.get();
|
||||
final List<MediaItem> items = MappingUtil.mapMediaItems(media);
|
||||
|
||||
new Handler(Looper.getMainLooper()).post(() -> {
|
||||
justStarted.set(true);
|
||||
browser.setMediaItems(items, startIndex, 0);
|
||||
|
|
@ -196,28 +199,31 @@ public class MediaManager {
|
|||
Player.Listener timelineListener = new Player.Listener() {
|
||||
@Override
|
||||
public void onTimelineChanged(Timeline timeline, int reason) {
|
||||
|
||||
int itemCount = browser.getMediaItemCount();
|
||||
if (itemCount > 0 && startIndex >= 0 && startIndex < itemCount) {
|
||||
browser.seekTo(startIndex, 0);
|
||||
browser.play();
|
||||
browser.removeListener(this);
|
||||
} else {
|
||||
Log.d(TAG, "Cannot start playback: itemCount=" + itemCount + ", startIndex=" + startIndex);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
browser.addListener(timelineListener);
|
||||
});
|
||||
|
||||
backgroundExecutor.execute(() -> {
|
||||
Log.d(TAG, "Background: enqueuing to database");
|
||||
enqueueDatabase(media, true, 0);
|
||||
});
|
||||
}
|
||||
} catch (ExecutionException | InterruptedException e) {
|
||||
Log.e(TAG, "Error executing startQueue logic: " + e.getMessage(), e);
|
||||
Log.e(TAG, "Error in startQueue: " + e.getMessage(), e);
|
||||
}
|
||||
}, MoreExecutors.directExecutor());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public static void startQueue(ListenableFuture<MediaBrowser> mediaBrowserListenableFuture, Child media) {
|
||||
|
|
@ -442,7 +448,7 @@ public class MediaManager {
|
|||
if (mediaItem != null && Preferences.isContinuousPlayEnabled() && Preferences.isInstantMixUsable()) {
|
||||
Preferences.setLastInstantMix();
|
||||
|
||||
LiveData<List<Child>> instantMix = getSongRepository().getInstantMix(mediaItem.mediaId, 10);
|
||||
LiveData<List<Child>> instantMix = getSongRepository().getInstantMix(mediaItem.mediaId, SeedType.TRACK, 10);
|
||||
instantMix.observeForever(new Observer<List<Child>>() {
|
||||
@Override
|
||||
public void onChanged(List<Child> media) {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
package com.cappielloantonio.tempo.subsonic.models
|
||||
|
||||
import androidx.annotation.Keep
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
@Keep
|
||||
class SimilarSongs {
|
||||
@SerializedName("song")
|
||||
var songs: List<Child>? = null
|
||||
}
|
||||
|
|
@ -9,12 +9,12 @@ import android.util.Log;
|
|||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Toast;
|
||||
import android.widget.ToggleButton;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.lifecycle.Observer;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.session.MediaBrowser;
|
||||
|
|
@ -32,6 +32,7 @@ import com.cappielloantonio.tempo.interfaces.ClickCallback;
|
|||
import com.cappielloantonio.tempo.service.MediaManager;
|
||||
import com.cappielloantonio.tempo.service.MediaService;
|
||||
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
||||
import com.cappielloantonio.tempo.ui.adapter.AlbumCatalogueAdapter;
|
||||
import com.cappielloantonio.tempo.ui.adapter.ArtistCatalogueAdapter;
|
||||
|
|
@ -203,22 +204,27 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void initPlayButtons() {
|
||||
bind.artistPageShuffleButton.setOnClickListener(v -> artistPageViewModel.getArtistShuffleList().observe(getViewLifecycleOwner(), songs -> {
|
||||
if (!songs.isEmpty()) {
|
||||
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
|
||||
activity.setBottomSheetInPeek(true);
|
||||
} else {
|
||||
Toast.makeText(requireContext(), getString(R.string.artist_error_retrieving_tracks), Toast.LENGTH_SHORT).show();
|
||||
bind.artistPageShuffleButton.setOnClickListener(v -> artistPageViewModel.getArtistShuffleList().observe(getViewLifecycleOwner(), new Observer<List<Child>>() {
|
||||
@Override
|
||||
public void onChanged(List<Child> songs) {
|
||||
if (songs != null && !songs.isEmpty()) {
|
||||
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
|
||||
activity.setBottomSheetInPeek(true);
|
||||
artistPageViewModel.getArtistShuffleList().removeObserver(this);
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
bind.artistPageRadioButton.setOnClickListener(v -> artistPageViewModel.getArtistInstantMix().observe(getViewLifecycleOwner(), songs -> {
|
||||
if (songs != null && !songs.isEmpty()) {
|
||||
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
|
||||
activity.setBottomSheetInPeek(true);
|
||||
} else {
|
||||
Toast.makeText(requireContext(), getString(R.string.artist_error_retrieving_radio), Toast.LENGTH_SHORT).show();
|
||||
bind.artistPageRadioButton.setOnClickListener(v -> artistPageViewModel.getArtistInstantMix().observe(getViewLifecycleOwner(), new Observer<List<Child>>() {
|
||||
@Override
|
||||
public void onChanged(List<Child> songs) {
|
||||
if (songs != null && !songs.isEmpty()) {
|
||||
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
|
||||
activity.setBottomSheetInPeek(true);
|
||||
artistPageViewModel.getArtistInstantMix().removeObserver(this);
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import android.content.Intent;
|
|||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
|
@ -1253,20 +1255,25 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
|||
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMediaClick(Bundle bundle) {
|
||||
if (bundle.containsKey(Constants.MEDIA_MIX)) {
|
||||
MediaManager.startQueue(mediaBrowserListenableFuture, bundle.getParcelable(Constants.TRACK_OBJECT));
|
||||
Child track = bundle.getParcelable(Constants.TRACK_OBJECT);
|
||||
activity.setBottomSheetInPeek(true);
|
||||
|
||||
if (mediaBrowserListenableFuture != null) {
|
||||
homeViewModel.getMediaInstantMix(getViewLifecycleOwner(), bundle.getParcelable(Constants.TRACK_OBJECT)).observe(getViewLifecycleOwner(), songs -> {
|
||||
MusicUtil.ratingFilter(songs);
|
||||
final boolean[] playbackStarted = {false};
|
||||
|
||||
if (songs != null && !songs.isEmpty()) {
|
||||
MediaManager.enqueue(mediaBrowserListenableFuture, songs, true);
|
||||
}
|
||||
});
|
||||
homeViewModel.getMediaInstantMix(getViewLifecycleOwner(), track)
|
||||
.observe(getViewLifecycleOwner(), songs -> {
|
||||
if (playbackStarted[0] || songs == null || songs.isEmpty()) return;
|
||||
|
||||
new Handler(Looper.getMainLooper()).postDelayed(() -> {
|
||||
if (playbackStarted[0]) return;
|
||||
|
||||
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
|
||||
playbackStarted[0] = true;
|
||||
}, 300);
|
||||
});
|
||||
}
|
||||
} else if (bundle.containsKey(Constants.MEDIA_CHRONOLOGY)) {
|
||||
List<Child> media = bundle.getParcelableArrayList(Constants.TRACKS_OBJECT);
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import android.content.ClipboardManager;
|
|||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
|
@ -43,7 +44,6 @@ import com.cappielloantonio.tempo.util.ExternalAudioReader;
|
|||
import com.cappielloantonio.tempo.viewmodel.AlbumBottomSheetViewModel;
|
||||
import com.cappielloantonio.tempo.viewmodel.HomeViewModel;
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
|
@ -61,7 +61,11 @@ public class AlbumBottomSheetDialog extends BottomSheetDialogFragment implements
|
|||
private List<Child> currentAlbumTracks = Collections.emptyList();
|
||||
private List<MediaItem> currentAlbumMediaItems = Collections.emptyList();
|
||||
|
||||
private boolean playbackStarted = false;
|
||||
private boolean dismissalScheduled = false;
|
||||
|
||||
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
|
||||
private static final String TAG = "AlbumBottomSheetDialog";
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
|
|
@ -114,33 +118,74 @@ public class AlbumBottomSheetDialog extends BottomSheetDialogFragment implements
|
|||
|
||||
ToggleButton favoriteToggle = view.findViewById(R.id.button_favorite);
|
||||
favoriteToggle.setChecked(albumBottomSheetViewModel.getAlbum().getStarred() != null);
|
||||
favoriteToggle.setOnClickListener(v -> {
|
||||
albumBottomSheetViewModel.setFavorite(requireContext());
|
||||
});
|
||||
favoriteToggle.setOnClickListener(v -> albumBottomSheetViewModel.setFavorite(requireContext()));
|
||||
|
||||
TextView playRadio = view.findViewById(R.id.play_radio_text_view);
|
||||
playRadio.setOnClickListener(v -> {
|
||||
AlbumRepository albumRepository = new AlbumRepository();
|
||||
albumRepository.getInstantMix(album, 20, new MediaCallback() {
|
||||
playbackStarted = false;
|
||||
dismissalScheduled = false;
|
||||
Toast.makeText(requireContext(), R.string.bottom_sheet_generating_instant_mix, Toast.LENGTH_SHORT).show();
|
||||
final Runnable failsafeTimeout = () -> {
|
||||
if (!playbackStarted && !dismissalScheduled) {
|
||||
Log.w(TAG, "No response received within 3 seconds");
|
||||
if (isAdded() && getActivity() != null) {
|
||||
Toast.makeText(getContext(),
|
||||
R.string.bottom_sheet_problem_generating_instant_mix,
|
||||
Toast.LENGTH_SHORT).show();
|
||||
dismissBottomSheet();
|
||||
}
|
||||
}
|
||||
};
|
||||
view.postDelayed(failsafeTimeout, 3000);
|
||||
|
||||
new AlbumRepository().getInstantMix(album, 20, new MediaCallback() {
|
||||
@Override
|
||||
public void onError(Exception exception) {
|
||||
exception.printStackTrace();
|
||||
view.removeCallbacks(failsafeTimeout);
|
||||
Log.e(TAG, "Error: " + exception.getMessage());
|
||||
if (isAdded() && getActivity() != null) {
|
||||
String message = isOffline(exception) ?
|
||||
"You're offline" : "Network error";
|
||||
Toast.makeText(getContext(), message, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
if (!playbackStarted && !dismissalScheduled) {
|
||||
scheduleDelayedDismissal(v);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadMedia(List<?> media) {
|
||||
view.removeCallbacks(failsafeTimeout);
|
||||
if (!isAdded() || getActivity() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
MusicUtil.ratingFilter((ArrayList<Child>) media);
|
||||
|
||||
if (!media.isEmpty()) {
|
||||
boolean isFirstBatch = !playbackStarted;
|
||||
MediaManager.startQueue(mediaBrowserListenableFuture, (ArrayList<Child>) media, 0);
|
||||
((MainActivity) requireActivity()).setBottomSheetInPeek(true);
|
||||
playbackStarted = true;
|
||||
|
||||
if (getActivity() instanceof MainActivity) {
|
||||
((MainActivity) getActivity()).setBottomSheetInPeek(true);
|
||||
}
|
||||
if (isFirstBatch && !dismissalScheduled) {
|
||||
scheduleDelayedDismissal(v);
|
||||
}
|
||||
} else {
|
||||
Toast.makeText(getContext(),
|
||||
R.string.bottom_sheet_problem_generating_instant_mix,
|
||||
Toast.LENGTH_SHORT).show();
|
||||
if (!playbackStarted && !dismissalScheduled) {
|
||||
scheduleDelayedDismissal(v);
|
||||
}
|
||||
}
|
||||
|
||||
dismissBottomSheet();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
TextView playRandom = view.findViewById(R.id.play_random_text_view);
|
||||
playRandom.setOnClickListener(v -> {
|
||||
AlbumRepository albumRepository = new AlbumRepository();
|
||||
|
|
@ -186,18 +231,16 @@ public class AlbumBottomSheetDialog extends BottomSheetDialogFragment implements
|
|||
});
|
||||
|
||||
TextView addToPlaylist = view.findViewById(R.id.add_to_playlist_text_view);
|
||||
addToPlaylist.setOnClickListener(v -> {
|
||||
albumBottomSheetViewModel.getAlbumTracks().observe(getViewLifecycleOwner(), songs -> {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putParcelableArrayList(Constants.TRACKS_OBJECT, new ArrayList<>(songs));
|
||||
addToPlaylist.setOnClickListener(v -> albumBottomSheetViewModel.getAlbumTracks().observe(getViewLifecycleOwner(), songs -> {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putParcelableArrayList(Constants.TRACKS_OBJECT, new ArrayList<>(songs));
|
||||
|
||||
PlaylistChooserDialog dialog = new PlaylistChooserDialog();
|
||||
dialog.setArguments(bundle);
|
||||
dialog.show(requireActivity().getSupportFragmentManager(), null);
|
||||
PlaylistChooserDialog dialog = new PlaylistChooserDialog();
|
||||
dialog.setArguments(bundle);
|
||||
dialog.show(requireActivity().getSupportFragmentManager(), null);
|
||||
|
||||
dismissBottomSheet();
|
||||
});
|
||||
});
|
||||
dismissBottomSheet();
|
||||
}));
|
||||
|
||||
removeAllTextView = view.findViewById(R.id.remove_all_text_view);
|
||||
albumBottomSheetViewModel.getAlbumTracks().observe(getViewLifecycleOwner(), songs -> {
|
||||
|
|
@ -291,4 +334,31 @@ public class AlbumBottomSheetDialog extends BottomSheetDialogFragment implements
|
|||
private void refreshShares() {
|
||||
homeViewModel.refreshShares(requireActivity());
|
||||
}
|
||||
|
||||
private void scheduleDelayedDismissal(View view) {
|
||||
if (dismissalScheduled) return;
|
||||
dismissalScheduled = true;
|
||||
|
||||
view.postDelayed(() -> {
|
||||
try {
|
||||
if (mediaBrowserListenableFuture.isDone()) {
|
||||
MediaBrowser browser = mediaBrowserListenableFuture.get();
|
||||
if (browser != null && browser.isPlaying()) {
|
||||
dismissBottomSheet();
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error checking playback: " + e.getMessage());
|
||||
}
|
||||
view.postDelayed(() -> dismissBottomSheet(), 200);
|
||||
}, 300);
|
||||
}
|
||||
|
||||
private boolean isOffline(Exception exception) {
|
||||
return exception != null && exception.getMessage() != null &&
|
||||
(exception.getMessage().contains("Network") ||
|
||||
exception.getMessage().contains("timeout") ||
|
||||
exception.getMessage().contains("offline"));
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package com.cappielloantonio.tempo.ui.fragment.bottomsheetdialog;
|
|||
|
||||
import android.content.ComponentName;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
|
@ -18,10 +19,12 @@ import androidx.media3.session.SessionToken;
|
|||
|
||||
import com.cappielloantonio.tempo.R;
|
||||
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
|
||||
import com.cappielloantonio.tempo.interfaces.MediaCallback;
|
||||
import com.cappielloantonio.tempo.repository.ArtistRepository;
|
||||
import com.cappielloantonio.tempo.service.MediaManager;
|
||||
import com.cappielloantonio.tempo.service.MediaService;
|
||||
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
||||
import com.cappielloantonio.tempo.util.Constants;
|
||||
import com.cappielloantonio.tempo.util.MusicUtil;
|
||||
|
|
@ -29,6 +32,9 @@ import com.cappielloantonio.tempo.viewmodel.ArtistBottomSheetViewModel;
|
|||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@UnstableApi
|
||||
public class ArtistBottomSheetDialog extends BottomSheetDialogFragment implements View.OnClickListener {
|
||||
private static final String TAG = "AlbumBottomSheetDialog";
|
||||
|
|
@ -38,6 +44,9 @@ public class ArtistBottomSheetDialog extends BottomSheetDialogFragment implement
|
|||
|
||||
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
|
||||
|
||||
private boolean playbackStarted = false;
|
||||
private boolean dismissalScheduled = false;
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
|
|
@ -86,20 +95,69 @@ public class ArtistBottomSheetDialog extends BottomSheetDialogFragment implement
|
|||
|
||||
TextView playRadio = view.findViewById(R.id.play_radio_text_view);
|
||||
playRadio.setOnClickListener(v -> {
|
||||
ArtistRepository artistRepository = new ArtistRepository();
|
||||
Log.d(TAG, "Artist instant mix clicked");
|
||||
Toast.makeText(requireContext(), R.string.bottom_sheet_generating_instant_mix, Toast.LENGTH_SHORT).show();
|
||||
playbackStarted = false;
|
||||
dismissalScheduled = false;
|
||||
final Runnable failsafeTimeout = () -> {
|
||||
if (!playbackStarted && !dismissalScheduled) {
|
||||
Log.w(TAG, "No response received within 3 seconds");
|
||||
if (isAdded() && getActivity() != null) {
|
||||
Toast.makeText(getContext(),
|
||||
R.string.bottom_sheet_problem_generating_instant_mix,
|
||||
Toast.LENGTH_SHORT).show();
|
||||
dismissBottomSheet();
|
||||
}
|
||||
}
|
||||
};
|
||||
view.postDelayed(failsafeTimeout, 3000);
|
||||
|
||||
artistRepository.getInstantMix(artist, 20).observe(getViewLifecycleOwner(), songs -> {
|
||||
// navidrome may return null for this
|
||||
if (songs == null)
|
||||
return;
|
||||
MusicUtil.ratingFilter(songs);
|
||||
|
||||
if (!songs.isEmpty()) {
|
||||
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
|
||||
((MainActivity) requireActivity()).setBottomSheetInPeek(true);
|
||||
new ArtistRepository().getInstantMix(artist, 20, new MediaCallback() {
|
||||
@Override
|
||||
public void onError(Exception exception) {
|
||||
view.removeCallbacks(failsafeTimeout);
|
||||
Log.e(TAG, "Error: " + exception.getMessage());
|
||||
if (isAdded() && getActivity() != null) {
|
||||
String message = isOffline(exception) ?
|
||||
"You're offline" : "Network error";
|
||||
Toast.makeText(getContext(), message, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
if (!playbackStarted && !dismissalScheduled) {
|
||||
scheduleDelayedDismissal(v);
|
||||
}
|
||||
}
|
||||
|
||||
dismissBottomSheet();
|
||||
@Override
|
||||
public void onLoadMedia(List<?> media) {
|
||||
view.removeCallbacks(failsafeTimeout);
|
||||
if (!isAdded() || getActivity() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Log.d(TAG, "Received " + media.size() + " songs for artist");
|
||||
|
||||
MusicUtil.ratingFilter((ArrayList<Child>) media);
|
||||
|
||||
if (!media.isEmpty()) {
|
||||
boolean isFirstBatch = !playbackStarted;
|
||||
MediaManager.startQueue(mediaBrowserListenableFuture, (ArrayList<Child>) media, 0);
|
||||
playbackStarted = true;
|
||||
|
||||
if (getActivity() instanceof MainActivity) {
|
||||
((MainActivity) getActivity()).setBottomSheetInPeek(true);
|
||||
}
|
||||
if (isFirstBatch && !dismissalScheduled) {
|
||||
scheduleDelayedDismissal(v);
|
||||
}
|
||||
} else {
|
||||
Toast.makeText(getContext(),
|
||||
R.string.bottom_sheet_problem_generating_instant_mix,
|
||||
Toast.LENGTH_SHORT).show();
|
||||
if (!playbackStarted && !dismissalScheduled) {
|
||||
scheduleDelayedDismissal(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -108,16 +166,10 @@ public class ArtistBottomSheetDialog extends BottomSheetDialogFragment implement
|
|||
ArtistRepository artistRepository = new ArtistRepository();
|
||||
artistRepository.getRandomSong(artist, 50).observe(getViewLifecycleOwner(), songs -> {
|
||||
MusicUtil.ratingFilter(songs);
|
||||
|
||||
if (!songs.isEmpty()) {
|
||||
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
|
||||
((MainActivity) requireActivity()).setBottomSheetInPeek(true);
|
||||
|
||||
dismissBottomSheet();
|
||||
} else {
|
||||
Toast.makeText(requireContext(), getString(R.string.artist_error_retrieving_tracks), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
dismissBottomSheet();
|
||||
});
|
||||
});
|
||||
|
|
@ -139,4 +191,31 @@ public class ArtistBottomSheetDialog extends BottomSheetDialogFragment implement
|
|||
private void releaseMediaBrowser() {
|
||||
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
|
||||
}
|
||||
}
|
||||
|
||||
private void scheduleDelayedDismissal(View view) {
|
||||
if (dismissalScheduled) return;
|
||||
dismissalScheduled = true;
|
||||
|
||||
view.postDelayed(() -> {
|
||||
try {
|
||||
if (mediaBrowserListenableFuture.isDone()) {
|
||||
MediaBrowser browser = mediaBrowserListenableFuture.get();
|
||||
if (browser != null && browser.isPlaying()) {
|
||||
dismissBottomSheet();
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error checking playback: " + e.getMessage());
|
||||
}
|
||||
view.postDelayed(() -> dismissBottomSheet(), 200);
|
||||
}, 300);
|
||||
}
|
||||
|
||||
private boolean isOffline(Exception exception) {
|
||||
return exception != null && exception.getMessage() != null &&
|
||||
(exception.getMessage().contains("Network") ||
|
||||
exception.getMessage().contains("timeout") ||
|
||||
exception.getMessage().contains("offline"));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,9 @@ import android.content.ClipboardManager;
|
|||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
|
@ -23,6 +26,7 @@ import androidx.navigation.fragment.NavHostFragment;
|
|||
|
||||
import com.cappielloantonio.tempo.R;
|
||||
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
|
||||
import com.cappielloantonio.tempo.interfaces.MediaCallback;
|
||||
import com.cappielloantonio.tempo.model.Download;
|
||||
import com.cappielloantonio.tempo.service.MediaManager;
|
||||
import com.cappielloantonio.tempo.service.MediaService;
|
||||
|
|
@ -50,6 +54,7 @@ import com.cappielloantonio.tempo.util.ExternalAudioWriter;
|
|||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@UnstableApi
|
||||
public class SongBottomSheetDialog extends BottomSheetDialogFragment implements View.OnClickListener {
|
||||
|
|
@ -67,7 +72,11 @@ public class SongBottomSheetDialog extends BottomSheetDialogFragment implements
|
|||
private AssetLinkUtil.AssetLink currentAlbumLink;
|
||||
private AssetLinkUtil.AssetLink currentArtistLink;
|
||||
|
||||
private boolean playbackStarted = false;
|
||||
private boolean dismissalScheduled = false;
|
||||
|
||||
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
|
||||
private static final String TAG = "SongBottomSheetDialog";
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
|
|
@ -143,22 +152,68 @@ public class SongBottomSheetDialog extends BottomSheetDialogFragment implements
|
|||
|
||||
TextView playRadio = view.findViewById(R.id.play_radio_text_view);
|
||||
playRadio.setOnClickListener(v -> {
|
||||
MediaManager.startQueue(mediaBrowserListenableFuture, song);
|
||||
((MainActivity) requireActivity()).setBottomSheetInPeek(true);
|
||||
playbackStarted = false;
|
||||
dismissalScheduled = false;
|
||||
Toast.makeText(requireContext(), R.string.bottom_sheet_generating_instant_mix, Toast.LENGTH_SHORT).show();
|
||||
|
||||
songBottomSheetViewModel.getInstantMix(getViewLifecycleOwner(), song).observe(getViewLifecycleOwner(), songs -> {
|
||||
MusicUtil.ratingFilter(songs);
|
||||
|
||||
if (songs == null) {
|
||||
dismissBottomSheet();
|
||||
return;
|
||||
final Runnable failsafeTimeout = () -> {
|
||||
if (!playbackStarted && !dismissalScheduled) {
|
||||
Log.w(TAG, "No response received within 3 seconds");
|
||||
if (isAdded() && getActivity() != null) {
|
||||
Toast.makeText(getContext(),
|
||||
R.string.bottom_sheet_problem_generating_instant_mix,
|
||||
Toast.LENGTH_SHORT).show();
|
||||
dismissBottomSheet();
|
||||
}
|
||||
}
|
||||
};
|
||||
view.postDelayed(failsafeTimeout, 3000);
|
||||
songBottomSheetViewModel.getInstantMix(song, 20, new MediaCallback() {
|
||||
@Override
|
||||
public void onError(Exception exception) {
|
||||
view.removeCallbacks(failsafeTimeout);
|
||||
Log.e(TAG, "Error: " + exception.getMessage());
|
||||
if (isAdded() && getActivity() != null) {
|
||||
String message = isOffline(exception) ?
|
||||
"You're offline" : "Network error";
|
||||
Toast.makeText(getContext(), message, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
if (!playbackStarted && !dismissalScheduled) {
|
||||
scheduleDelayedDismissal(v);
|
||||
}
|
||||
}
|
||||
|
||||
if (!songs.isEmpty()) {
|
||||
MediaManager.enqueue(mediaBrowserListenableFuture, songs, true);
|
||||
dismissBottomSheet();
|
||||
@Override
|
||||
public void onLoadMedia(List<?> media) {
|
||||
view.removeCallbacks(failsafeTimeout);
|
||||
if (!isAdded() || getActivity() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
MusicUtil.ratingFilter((ArrayList<Child>) media);
|
||||
|
||||
if (!media.isEmpty()) {
|
||||
boolean isFirstBatch = !playbackStarted;
|
||||
MediaManager.startQueue(mediaBrowserListenableFuture, (ArrayList<Child>) media, 0);
|
||||
playbackStarted = true;
|
||||
|
||||
if (getActivity() instanceof MainActivity) {
|
||||
((MainActivity) getActivity()).setBottomSheetInPeek(true);
|
||||
}
|
||||
if (isFirstBatch && !dismissalScheduled) {
|
||||
scheduleDelayedDismissal(v);
|
||||
}
|
||||
} else {
|
||||
Toast.makeText(getContext(),
|
||||
R.string.bottom_sheet_problem_generating_instant_mix,
|
||||
Toast.LENGTH_SHORT).show();
|
||||
if (!playbackStarted && !dismissalScheduled) {
|
||||
scheduleDelayedDismissal(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
TextView playNext = view.findViewById(R.id.play_next_text_view);
|
||||
|
|
@ -397,4 +452,31 @@ public class SongBottomSheetDialog extends BottomSheetDialogFragment implements
|
|||
private void refreshShares() {
|
||||
homeViewModel.refreshShares(requireActivity());
|
||||
}
|
||||
|
||||
private void scheduleDelayedDismissal(View view) {
|
||||
if (dismissalScheduled) return;
|
||||
dismissalScheduled = true;
|
||||
|
||||
view.postDelayed(() -> {
|
||||
try {
|
||||
if (mediaBrowserListenableFuture.isDone()) {
|
||||
MediaBrowser browser = mediaBrowserListenableFuture.get();
|
||||
if (browser != null && browser.isPlaying()) {
|
||||
dismissBottomSheet();
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error checking playback: " + e.getMessage());
|
||||
}
|
||||
view.postDelayed(() -> dismissBottomSheet(), 200);
|
||||
}, 300);
|
||||
}
|
||||
|
||||
private boolean isOffline(Exception exception) {
|
||||
return exception != null && exception.getMessage() != null &&
|
||||
(exception.getMessage().contains("Network") ||
|
||||
exception.getMessage().contains("timeout") ||
|
||||
exception.getMessage().contains("offline"));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -133,4 +133,7 @@ object Constants {
|
|||
const val CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_OFF = "android.media3.session.demo.REPEAT_OFF"
|
||||
const val CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_ONE = "android.media3.session.demo.REPEAT_ONE"
|
||||
const val CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_ALL = "android.media3.session.demo.REPEAT_ALL"
|
||||
enum class SeedType {
|
||||
ARTIST, ALBUM, TRACK
|
||||
}
|
||||
}
|
||||
|
|
@ -4,10 +4,12 @@ import android.app.Application;
|
|||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.OptIn;
|
||||
import androidx.lifecycle.AndroidViewModel;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.Observer;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
|
||||
import com.cappielloantonio.tempo.model.Download;
|
||||
import com.cappielloantonio.tempo.interfaces.StarCallback;
|
||||
|
|
@ -33,7 +35,6 @@ public class AlbumBottomSheetViewModel extends AndroidViewModel {
|
|||
private final ArtistRepository artistRepository;
|
||||
private final FavoriteRepository favoriteRepository;
|
||||
private final SharingRepository sharingRepository;
|
||||
|
||||
private AlbumID3 album;
|
||||
|
||||
public AlbumBottomSheetViewModel(@NonNull Application application) {
|
||||
|
|
@ -116,6 +117,7 @@ public class AlbumBottomSheetViewModel extends AndroidViewModel {
|
|||
MutableLiveData<List<Child>> tracksLiveData = albumRepository.getAlbumTracks(album.getId());
|
||||
|
||||
tracksLiveData.observeForever(new Observer<List<Child>>() {
|
||||
@OptIn(markerClass = UnstableApi.class)
|
||||
@Override
|
||||
public void onChanged(List<Child> songs) {
|
||||
if (songs != null && !songs.isEmpty()) {
|
||||
|
|
|
|||
|
|
@ -128,7 +128,6 @@ public class ArtistPageViewModel extends AndroidViewModel {
|
|||
MappingUtil.mapDownloads(songs),
|
||||
songs.stream().map(Download::new).collect(Collectors.toList())
|
||||
);
|
||||
} else {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
|
|||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Playlist;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Share;
|
||||
import com.cappielloantonio.tempo.util.Constants.SeedType;
|
||||
import com.cappielloantonio.tempo.util.Preferences;
|
||||
import com.google.common.reflect.TypeToken;
|
||||
import com.google.gson.Gson;
|
||||
|
|
@ -34,7 +35,6 @@ import java.util.Collections;
|
|||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class HomeViewModel extends AndroidViewModel {
|
||||
private static final String TAG = "HomeViewModel";
|
||||
|
|
@ -223,7 +223,7 @@ public class HomeViewModel extends AndroidViewModel {
|
|||
public LiveData<List<Child>> getMediaInstantMix(LifecycleOwner owner, Child media) {
|
||||
mediaInstantMix.setValue(Collections.emptyList());
|
||||
|
||||
songRepository.getInstantMix(media.getId(), 20).observe(owner, mediaInstantMix::postValue);
|
||||
songRepository.getInstantMix(media.getId(), SeedType.TRACK, 20).observe(owner, mediaInstantMix::postValue);
|
||||
|
||||
return mediaInstantMix;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ import androidx.lifecycle.LiveData;
|
|||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.Observer;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.session.MediaBrowser;
|
||||
|
||||
import com.cappielloantonio.tempo.interfaces.StarCallback;
|
||||
import com.cappielloantonio.tempo.model.Download;
|
||||
|
|
@ -278,7 +277,7 @@ public class PlayerBottomSheetViewModel extends AndroidViewModel {
|
|||
public LiveData<List<Child>> getMediaInstantMix(LifecycleOwner owner, Child media) {
|
||||
instantMix.setValue(Collections.emptyList());
|
||||
|
||||
songRepository.getInstantMix(media.getId(), 20).observe(owner, instantMix::postValue);
|
||||
songRepository.getInstantMix(media.getId(), Constants.SeedType.TRACK, 20).observe(owner, instantMix::postValue);
|
||||
|
||||
return instantMix;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import androidx.lifecycle.LiveData;
|
|||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
|
||||
import com.cappielloantonio.tempo.interfaces.MediaCallback;
|
||||
import com.cappielloantonio.tempo.interfaces.StarCallback;
|
||||
import com.cappielloantonio.tempo.model.Download;
|
||||
import com.cappielloantonio.tempo.repository.AlbumRepository;
|
||||
|
|
@ -21,6 +22,7 @@ import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
|
|||
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Share;
|
||||
import com.cappielloantonio.tempo.util.Constants.SeedType;
|
||||
import com.cappielloantonio.tempo.util.DownloadUtil;
|
||||
import com.cappielloantonio.tempo.util.MappingUtil;
|
||||
import com.cappielloantonio.tempo.util.NetworkUtil;
|
||||
|
|
@ -128,11 +130,22 @@ public class SongBottomSheetViewModel extends AndroidViewModel {
|
|||
public LiveData<List<Child>> getInstantMix(LifecycleOwner owner, Child media) {
|
||||
instantMix.setValue(Collections.emptyList());
|
||||
|
||||
songRepository.getInstantMix(media.getId(), 20).observe(owner, instantMix::postValue);
|
||||
songRepository.getInstantMix(media.getId(), SeedType.TRACK, 20).observe(owner, instantMix::postValue);
|
||||
|
||||
return instantMix;
|
||||
}
|
||||
|
||||
public void getInstantMix(Child media, int count, MediaCallback callback) {
|
||||
|
||||
songRepository.getInstantMix(media.getId(), SeedType.TRACK, count, songs -> {
|
||||
if (songs != null && !songs.isEmpty()) {
|
||||
callback.onLoadMedia(songs);
|
||||
} else {
|
||||
callback.onLoadMedia(Collections.emptyList());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public MutableLiveData<Share> shareTrack() {
|
||||
return sharingRepository.createShare(song.getId(), song.getTitle(), null);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,6 +51,8 @@
|
|||
<string name="battery_optimization_negative_button">Ignore</string>
|
||||
<string name="battery_optimization_neutral_button">Don\'t ask again</string>
|
||||
<string name="battery_optimization_positive_button">Disable</string>
|
||||
<string name="bottom_sheet_generating_instant_mix">Generating instant mix...</string>
|
||||
<string name="bottom_sheet_problem_generating_instant_mix">Could not retrieve tracks from subsonic server.</string>
|
||||
<string name="connection_alert_dialog_negative_button">Cancel</string>
|
||||
<string name="connection_alert_dialog_neutral_button">Enable data saver</string>
|
||||
<string name="connection_alert_dialog_positive_button">OK</string>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue